细说React生命周期
新旧版本生命周期图对比
16.3之前的版本
16.3之后的版本
react自16.x之后,添加了getDerivedStateFromProps, getSnapshotBeforeUpdate
,如图
生命周期的几个阶段
生命周期有几个时期,分别为挂载,更新,卸载,我们看上方16.3之后版本的图可以得出,其实每个时期还分为几个阶段:
- render阶段: 是指从到render函数执行完返回jsx结构之前的过程,这个过程的都是不包含副作用的纯函数,这个过程包含的函数有
- constructor
- getDerivedStateFromProps
- shouldUpdateComponent
- render
- getDerivedStateFromError
- pre-commit阶段: 是指从返回一个jsx结构到更新dom之前的过程,这个过程只有函数
getSnapshotBeforeUpdate
- commit阶段: 是指更新dom,以及更新成功之后的过程,有函数
- componentDidMount
- componentDidUpdate
- componentDidCatch
从上图中看函数componentWillUnmount
是在commit阶段但是,我理解这个函数应该是在dom更新之前执行,我认为应该在pre-commit
挂载
所谓挂载其实就是组件第一次在dom树种渲染的过程,在这个过程中会一次执行一些生命周期函数
constructor
构造函数用于初始化props,state以及绑定this指向,如果不需要这些操作可以省略构造函数,需要注意的是
- 构造函数中不能调用setState, 而应该直接给state赋值
- props是由外部传进来的,所以不能修改props
- 不能直将props中的值赋值给state,像这样
constructor(props) {
super(props);
// 不要这样做
this.state = { color: props.color };
}
conpomentWillMount(v17将移除)
这个方法会在render之前执行,这个方法也是在服务端渲染中唯一会调用的方法
getDerivedStateFromProps(v16.3加入)
conpomentWillMount
移除了取而代之的就是这个方法,这是一个静态方法,实例无权访问,在组件实例化之后,以及每次render
之前调用。getDerivedStateFromProps
的存在只有一个目的就是让props更新的时候更新state,在这一点上跟componentWillReceiveProps
是一致的,这两个生命周期函数并不是只有在props更新的时候才会调用,而是当父组件重新渲染后,两个生命周期函数都会调用
static getDerivedStateFromProps(props, state)
render
组件的render函数并不会像dom中插入元素,实际上render只是返回jsx结构,或者是数组,字符串等数据,最终由ReactDOM.render渲染到dom中,在自定义的类组件中,render函数必须实现,并且render应该为一个根据state和props来返回结果,不造成副作用的纯函数
componentDidMount
在组件被挂载(插入到dom节点)后,立刻执行这个函数,可以在这个函数中请求数据,以及添加订阅,但是要记得在函数componentWillUnmount
中去取消订阅
更新
当组件的props更新时,或者state更新时也会触发一些生命周期函数来更新dom
props更新 componentWillReceiveProps(v17将移除)
当父组件的render被调用时,无论传进来的props是否更新,这个函数都会调用,需要注意的是,在函数调用时,应该将参数与props做一个对比,判断是否要更新state
componentWillReceiveProps(nextProps) {
// 只要 props.email 改变,就改变 state
if (nextProps.email !== this.props.email) {
this.setState({
email: nextProps.email
});
}
}
这个方法state更新时不会执行,v17之后由getDerivedStateFromProps
取代
shouldComponentUpdate
这个函数返回一个布尔值,返回true的时候接下来会执行组件的render
方法,默认情况下都是返回true的,如果为false则不会执行后续渲染,这个方法在挂载阶段或者执行forceUpdate
时不会执行。
shouldComponentUpdate(nextProps, nextState)
函数接受的参数为新的props
和新的state
所以在函数内部可以拿新的props和state与组件当前的props、state做一个对比,如果发现没有变化则返回false。React官方建议使用React.PureComponent
实现组件,这个组件的shouldComponentUpdate
方法会对props和state分别做浅层对比,以此来提高性能,同时不建议在shouldComponentUpdate
中去实现深层次的数据对比,以免影响效率
componentWillUpdate(v17将移除)
shouldComponentUpdate
返回true
,则会执行componentWillUpdate
,在这个方法中,不能执行setState
等其它会更新组件的方法(比如dispatch),可以在此方法中读取 DOM 信息(例如,为了保存滚动位置),而在之后可以放到getSnapshotBeforeUpdate
中
getSnapshotBeforeUpdate(v16.3引入)
getSnapshotBeforeUpdate(prevProps, prevState)
在render之后,dom更新之前回执行这个函数,一般会在此时读取dom更新之前的一些信息(比如滚动条位置),此函数的所有返回值,都将作为参数传给componentDidUpdate
componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot)
这里第三个参数就为上文中getSnapshotBeforeUpdate
的返回值,componentDidUpdate
会在dom加载完之后执行,在这个函数中就可以进行dom操作了
卸载
componentWillUnmount
组件销毁之前调用,在这里更新state不会更新组件
错误处理
react在v16引入了错误边界的概念,当子组件发生,当子组件的构造函数,生命周期以及子组件的构造函数出出错的时候,会执行错误处理回调
getDerivedStateFromError
static getDerivedStateFromError(error)
当子组件抛出错误会触发这个函数,错误信息为函数的参数,次函数可以返回一个新的值来更新state,组件可以根据更新的state实现一个降级UI,此方法在“render阶段”执行,应该为一个纯函数
componentDidCatch
componentDidCatch(error, info)
同样当子组件抛出错误的时候回触发,第二个参数为info,其中包含了组件调用栈的信息
console.log(info.componentStack);
这个函数是在“commit阶段”执行的,所以可以在函数中调用setState来更新state
为什么react要在v17移除部分构造函数?
官方团队认为,之前的几个will
生命周期函数会让用户产生很多误解,在实际的使用场景中,也有很多错误的使用情况出现。所以直接替换为一个静态方法getDerivedStateFromProps,这样用户就不能再这些方法内访问到当前的实例了,从而也就不能调用setState,或者访问refs了,减少了很多错误操作
其他原因可以参考链接