生命周期是我们在react中碰到的第二个新概念。如同人有生老病死,自然界有日月更替,每个组件也有被创建、更新和删除,如同有生命的机体一样。
react严格定义了组件的生命周期,生命周期可能会经历三个过程:
-
装载过程(mount):React.js 将组件渲染,并且构造 DOM 元素然后塞入页面的过程
-
更新过程(update):当组件被重新渲染的过程
-
卸载过程(unmount):组件从DOM中删除的过程
装载过程
装载过程依次调用的函数如下:
constructor:初始化state,绑定成员函数的this环境
//通过React.createClass这两个生命周期才会用到,getInitialState被constructor中的this.state代替了,getDefaultProps被static defaultProps代替了
React.createClass({
getInitialState:初始化state
getDefaultProps:提供默认的props
})
componentWillMount:在render之前调用,一般不再这个函数里里面做什么事,因为这时候dom还没有渲染出来,所有在这里可以做的事都可以在componentDidMount里面执行。可以在服务器端执行,也可以在浏览器端执行。在这里面执行setState,组件会更新state,但组件只渲染一次,因此这是无意义的执行,初始化的state都可以放到constructor里面去做
render:render函数并不做实际的渲染,它只是返回一个jsx,最终由react来操作渲染过程,它可以返回null或者false,等于告诉react这次不要渲染任何dom元素。render应该是一个纯函数,完全根据props、state来决定要返回的东西。render里面不能使用setState,因为一个纯函数不应该引起状态的改变
componentDidMount:在组件render之后执行,所有动画的启动,ajax请求、定时器皆可以放到这里面执行,这个函数也是用的最多的函数之一,这里面执行setState当然可以会再次更新组建。这个函数与render函数有一个不大不小的坑,可能有人认为render执行后立即调用componentDidMount,实际上却不是这样的。每一次render都是递归执行的,而之所以react这么设计是因为每个render函数返回的都是jsx表示的对象,然后由react根据返回对象来决定如何渲染,而react肯定是要把所有组件的结果综合起来,才能知道如何产生对应的DOM修改。所以只有当react调用所有组件的render之后才可能完成装载继而执行componentDidMount,所以componentDidMount才连在一起被调用。可以在浏览器端执行,不能在服务器端执行
示例
// ControlPanel
import React,{Component} from 'react'
import Counter from './Counter'
const style = {
margin:'20px'
}
export default class ControlPanel extends Component{
render(){
console.log('enter ControlPanel render');
return(
<div style={style}>
<Counter caption="First" />
<Counter caption="Second" initValue={10} />
<Counter caption="Third" initValue={20} />
<button onClick={ () => this.forceUpdate() }>
Click me to re-render!
</button>
</div>
)
}
}
//Counter
import React,{Component} from 'react'
import PropTypes from 'prop-types'
const buttonStyle = {
margin: '10px'
}
export default class Counter extends Component{
static propTypes = {
caption: PropTypes.string.isRequired,
initValue: PropTypes.number
}
static defaultProps = {
initValue: 0
}
constructor(props){
super(props)
this.onClickIncrementButton = this.onClickIncrementButton.bind(this)
this.onClickDecrementButton = this.onClickDecrementButton.bind(this)
this.state = {
count:props.initValue,
}
console.log('enter constructor: ' + props.caption);
}
componentWillMount() {
console.log('enter componentWillMount ' + this.props.caption);
}
componentDidMount() {
console.log('enter componentDidMount ' + this.props.caption);
}
componentWillReceiveProps(nextProps) {
console.log('enter componentWillReceiveProps ' + this.props.caption)
}
shouldComponentUpdate(nextProps, nextState) {
console.log('enter shouldComponentUpdate ' + this.props.caption)
return (nextProps.caption !== this.props.caption) ||
(nextState.count !== this.state.count);
//return true
}
componentWillUpdate(){
console.log('enter componentWillUpdate ' + this.props.caption)
}
componentDidUpdate(){
console.log('enter componentDidUpdate ' + this.props.caption)
}
onClickIncrementButton() {
this.setState({count: this.state.count + 1});
}
onClickDecrementButton() {
this.setState({count: this.state.count - 1});
}
render(){
console.log('enter render ' + this.props.caption);
return(
<div>
<button style={buttonStyle} onClick={this.onClickIncrementButton}>+</button>
<button style={buttonStyle} onClick={this.onClickDecrementButton}>-</button>
<span>{this.props.caption} count: {this.state.count}</span>
</div>
)
}
}
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import ControlPanel from './ControlPanel';
ReactDOM.render(<ControlPanel />, document.getElementById('root'));
结果:
enter ControlPanel render (父组件)
enter constructor first
enter componentWillMount first
enter render first
enter constructor second
enter componentWillMount second
enter render second
enter constructor third
enter componentWillMount third
enter render third
enter componentDidMount first
enter componentDidMount second
enter componentDidMount third
更新过程
componentWillReceiveProps:按字面意思是组件是否接受props,但是实际上只要父组件render函数被调用,在render函数里面的子组件就会经历更新过程,而不管父组件传给子组件的props有没有改变,都会触发子组件的componentWillProps,上面那个示例只要我点击re-render那个按钮强制更新父组件,明明传给子组件的props没有更新,但是控制台却打印了enter componentWillReceiveProps 。通过this.setState触发的更新过程不会调用这个函数,这是因为这个函数适合根据新的props值(也就是参数nextProps)来计算要不要更新内部状态state,而更新内部状态的方法就是setState,如果setState的调用导致componentWillReceiveProps再一次被调用,就会陷入一个死循环。虽然this.setState触发的更新过程不会调用这个函数,但是可以在这个函数中执行this.setState,虽然没什么用。
shouldComponentUpdate:render函数决定了该渲染什么,而shouldComponentUpdate决定了一个组件什么时候不需要渲染。而它们两个也是所有函数中唯二需要返回结果的函数,render的返回结果用于构造dom对象,而shouldComponentUpdate函数需要一个返回值,告诉react这个组件这次更新过程是否要继续。在更新过程中,react首先调用shouldComponentUpdate,若它返回true则继续更新,接下来调用render函数,若它返回false,就立即停止更新过程。它能够大大提高react的性能,如果确定特定组件在分析后很慢,则可以将其更改为从React.PureComponent继承,React.PureComponent实现了与浅层prop和state比较的shouldComponentUpdate()。如果你确信你想用手写它,你可以将this.props与nextProps和this.state与nextState进行比较,并返回false来告诉React更新可以被跳过。不能在这里调用setState,否则会造成循环调用。
componentWillUpdate:在更新之前做准备工作,如果我们想根据新的props值来计算要不要更新内部状态state,可以在componentWillReceiveProps 这个函数中来执行。同shouldComponentUpdate,不能在这里直接执行setState,会造成死循环,不能在这里执行直接setState,会造成死循环,但是可以设置一个边界条件,此时就可以setState了。
render:重新渲染
componentDidUpdate:可以在这里操纵dom以及进行网络请求,与
componentDidUpdate不同的是componentDidMount既可以在浏览器端执行又可以在服务器端执行。但是在服务器端只需产出html字符串,所以正常情况下也不会调用componentDidUpdate。值得一提的是由于render函数是递归调用,所以组件更新跟组件装载一样,我们的示例组件会先自己更新到render,最后几个componentDidUpdate依次调用。这个方法可以调用setState。
此时先在shouldComponentUpdate返回true才能看到效果,然后点击re-render按钮
enter ControlPanel render
enter componentWillReceiveProps First
enter shouldComponentUpdate First
enter componentWillUpdate First
enter render First
enter componentWillReceiveProps Second
enter shouldComponentUpdate Second
enter componentWillUpdate Second
enter render Second
enter componentWillReceiveProps Third
enter shouldComponentUpdate Third
enter componentWillUpdate Third
enter render Third
enter componentDidUpdate First
enter componentDidUpdate Second
enter componentDidUpdate Third
卸载过程
componentwillUnmout:组件卸载的时候调用,这个函数适合清理性的工作,比如清除定时器、清除在componentDidMount创造的dom元素。
以下是从网上找来的图,引用于深入react技术栈
生命周期整体流程图 生命周期全局图 生命周期执行顺序图
网友评论