React 生命周期
React生命周期主要包括4个阶段:
- 初始化阶段
- 实例化阶段
- 更新阶段
- 销毁阶段
1 设置组件的默认属性 getDefaultProps
对于每个组件实例来讲,这个方法只会调用一次,该组件类的所有后续应用,getDefaultPops 将不会再被调用,其返回的对象可以用于设置默认的 props(properties的缩写) 值。
getDefaultProps: function(){
return {
name: 'pomy',
git: 'dwqs'
}
},
-### 2 设置组件的初始化状态 getInitialState
对于组件的每个实例来说,这个方法的调用有且只有一次,用来初始化每个实例的 state,在这个方法里,可以访问组件的 props。每一个React组件都有自己的 state,其与 props 的区别在于 state只存在组件的内部,props 在所有实例中共享。
其返回值可以赋值给组件的this.state属性
getInitialState 和 getDefaultPops 的调用是有区别的,getDefaultPops 是对于组件类来说只调用一次,后续该类的应用都不会被调用,而 getInitialState 是对于每个组件实例来讲都会调用,并且只调一次。
var LikeButton = React.createClass({
getInitialState: function() {
return {liked: false};
},
handleClick: function(event) {
this.setState({liked: !this.state.liked});
},
render: function() {
var text = this.state.liked ? 'like' : 'haven\'t liked';
return (
<p onClick={this.handleClick}>
You {text} this. Click to toggle.
</p>
);
}
});
ReactDOM.render(
<LikeButton />,
document.getElementById('example')
);
每次修改 state,都会重新渲染组件,实例化后通过 state 更新组件,会依次调用下列方法:
1、shouldComponentUpdate
2、componentWillUpdate
3、render
4、componentDidUpdate
但是不要直接修改 this.state,要通过 this.setState 方法来修改。
getDefaultProps
相当于ES6中static defaultProps = {}
getInitialState
相当于constructor中的 this.state = {}
3 componentWillMount() 组件挂载之前
在render方法之前调用,主要做一些业务逻辑的处理。如,对state状态的操作;进行开启定时器、向服务器发送请求等操作
在组件挂载之前调用且全局只调用一次。如果在这个钩子里可以setState,render后可以看到更新后的state,不会触发重复渲染。该生命周期可以发起异步请求,并setState。(React v16.3后废弃该生命周期,可以在constructor中完成设置state
该方法在首次渲染之前调用,也是再 render 方法调用之前修改 state 的最后一次机会。
4 render()
组件渲染 根据state值,渲染并返回虚拟的DOM
render是一个React组件必须定义的生命周期,用来渲染dom。⚠️不要在render里面修改state,会触发死循环导致栈溢出。render必须返回reactDom
render() {
const {nodeResultData: {res} = {}} = this.props;
if (isEmpty(res)) return noDataInfo;
const nodeResult = this.getNodeResult(res);
return (
<div className="workspace-dialog-result">
{nodeResult}
</div>
);
该方法具有特殊的规则:
- 只能通过this.props和this.state访问数据
- 可以返回null、false或任何React组件
- 只能出现一个顶级组件(不能返回数组)
- 不能改变组件的状态
- 不能修改DOM的输出
render方法返回的结果并不是真正的DOM元素,而是一个虚拟的表现,类似于一个DOM tree的结构的对象。react之所以效率高,就是这个原因。
5 componentDidMount() 组件挂载完成后
组件已经被渲染到页面中后触发:此时页面中有了真正的DOM的元素,可以进行DOM相关的操作
在组件挂载完成后调用,且全局只调用一次。可以在这里使用refs,获取真实dom元素。该钩子内也可以发起异步请求,并在异步请求中可以进行setState。
componentDidMount() {
axios.get('/auth/getTemplate').then(res => {
const {TemplateList = []} = res;
this.setState({TemplateList});
});
}
挂载成功函数。该函数不会再render函数调用完成之后立即调用,因为render函数仅仅是返回了JSX的对象,并没有立即挂载到DOM树上,而componentDidMount是在组件被渲染到DOM树之后被调用的。另外,componentDidMount函数在进行服务器端渲染时不会被调用。
更新阶段
6 componentWillReceiveProps (nextProps ) props即将变化之前
props发生变化以及父组件重新渲染时都会触发该生命周期,在该钩子内可以通过参数nextProps获取变化后的props参数,通过this.props访问之前的props。该生命周期内可以进行setState。(React v16.3后废弃该生命周期,可以用新的周期 static getDerivedStateFromProps 代替)
componentWillReceiveProps: function(nextProps) {
if (nextProps.bool) {
this.setState({
bool: true
});
}
}
7 shouldComponentUpdate() 是否重新渲染
组件挂载之后,每次调用setState后都会调用shouldComponentUpdate判断是否需要重新渲染组件。默认返回true,需要重新render。返回fals则不会执行 render 以及后面的 componentWillUpdate,componentDidUpdate 方法。在比较复杂的应用里,有一些数据的改变并不影响界面展示,可以在这里做判断,优化渲染效率。
shouldComponentUpdate(newProps, newState) {
if (newProps.number < 5) return true;
return false
}
//该钩子函数可以接收到两个参数,新的属性和状态,返回true/false来控制组件是否需要更新。
一般我们通过该函数来优化性能:
一个React项目需要更新一个小组件时,很可能需要父组件更新自己的状态。而一个父组件的重新更新会造成它旗下所有的子组件重新执行render()方法,形成新的虚拟DOM,再用diff算法对新旧虚拟DOM进行结构和属性的比较,决定组件是否需要重新渲染
无疑这样的操作会造成很多的性能浪费,所以我们开发者可以根据项目的业务逻辑,在shouldComponentUpdate()中加入条件判断,从而优化性能
例如React中的就提供了一个PureComponent的类,当我们的组件继承于它时,组件更新时就会默认先比较新旧属性和状态,从而决定组件是否更新。值得注意的是,PureComponent进行的是浅比较,所以组件状态或属性改变时,都需要返回一个新的对象或数组
8 componentWillUpdate() 组件将要更新
shouldComponentUpdate返回true或者调用forceUpdate之后,componentWillUpdate会被调用。不能在该钩子中setState,会触发重复循环。(React v16.3后废弃该生命周期,可以用新的周期 getSnapshotBeforeUpdate )
9 render开始更新
10 componentDidUpdate()
这个方法和 componentDidMount 类似,在组件重新被渲染之后,componentDidUpdate(object prevProps, object prevState) 会被调用。可以在这里访问并修改 DOM。
除了首次render之后调用componentDidMount,其它render结束之后都是调用componentDidUpdate。该钩子内setState有可能会触发重复渲染,需要自行判断,否则会进入死循环。
componentDidUpdate() {
if(condition) {
this.setState({..}) // 设置state
} else {
// 不再设置state
}
}
11 componentWillUnmount() 组件即将被卸载
组件被销毁时触发。这里我们可以进行一些清理操作,例如清理定时器,取消Redux的订阅事件等等。
每当React使用完一个组件,这个组件必须从 DOM 中卸载后被销毁,此时 componentWillUnmout 会被执行,完成所有的清理和销毁工作,在 componentDidMount 中添加的任务都需要再该方法中撤销,如创建的定时器或事件监听器。
当再次装载组件时,以下方法会被依次调用:
1、getInitialState
2、componentWillMount
3、render
4、componentDidMount
setState
state:组件的属性,主要用来存储组件自身需要的数据,每次数据更新都是通过修改state属性的值,ReactJs内部会监听state属性的变化,一旦发生变化的,就会主动调用render方法更新虚拟dom结构。
要修改state,只能使用this.setState(),不能使用this.state.value=2类似方式设置state,一是不会驱动重新渲染,二是很可能被后面的操作替换,造成无法预知的错误。此外,React利用状态队列来实现setState的异步更新,避免频繁地重复更新state。
setState的调用是有风险的,在某些生命周期函数中调用可能会无用甚至早恒循环调用导致崩溃。state的初始化一般在构造函数中实现;setState可以在装载过程的componentWillMount、componentDidMount中调用;setState可以在更新过程中的componentWillReceiveProps、componentDidUpdate中调用。
虚拟dom
将真实的dom结构映射成一个Json数据结构。
例子
import React from 'react'
import ReactDOM from 'react-dom';
class SubCounter extends React.Component {
componentWillReceiveProps() {
console.log('9、子组件将要接收到新属性');
}
shouldComponentUpdate(newProps, newState) {
console.log('10、子组件是否需要更新');
if (newProps.number < 5) return true;
return false
}
componentWillUpdate() {
console.log('11、子组件将要更新');
}
componentDidUpdate() {
console.log('13、子组件更新完成');
}
componentWillUnmount() {
console.log('14、子组件将卸载');
}
render() {
console.log('12、子组件挂载中');
return (
<p>{this.props.number}</p>
)
}
}
class Counter extends React.Component {
static defaultProps = {
//1、加载默认属性
name: 'sls',
age:23
};
constructor() {
super();
//2、加载默认状态
this.state = {number: 0}
}
componentWillMount() {
console.log('3、父组件挂载之前');
}
componentDidMount() {
console.log('5、父组件挂载完成');
}
shouldComponentUpdate(newProps, newState) {
console.log('6、父组件是否需要更新');
if (newState.number<15) return true;
return false
}
componentWillUpdate() {
console.log('7、父组件将要更新');
}
componentDidUpdate() {
console.log('8、父组件更新完成');
}
handleClick = () => {
this.setState({
number: this.state.number + 1
})
};
render() {
console.log('4、render(父组件挂载)');
return (
<div>
<p>{this.state.number}</p>
<button onClick={this.handleClick}>+</button>
{this.state.number<10?<SubCounter number={this.state.number}/>:null}
</div>
)
}
}
ReactDOM.render(<Counter/>, document.getElementById('root'));