React 16 生命周期函数浅析
前言
刚学React的时候才 React16.1 刚出来每多久,那时候刚出来一个的钩子函数 componentDidCatch,可应用于处理错误边界。但 React v16.3 又新增了两个生命周期函数,前面写的那篇总结好像有点过时,因此又总结了一下,做个补充。
一、变动情况
React v16.3 发布时除了全新的 context API 之外,还引入了两个新的生命周期函数:
- getDerivedStateFromProps
- getSnapshotBeforeUpdate
并且也确定了在v17.0版本将移除三个生命周期函数:
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate。
总的来说就是移除了所有带 will 的生命周期函数,也就是 render 之前的生命周期函数除了shouldUpdateComponent 之外的生命周期函数都被干掉了。
二、变动详情
以我的理解,React 生命周期的变动主要是为了两方面:
- 迎合 Fiber 架构变动的需求
- 规范开发者开发的行为
2.1 getDerivedStateFromProps
首先以前需要利用被删除的那些生命周期函数才能实现的功能,都可以通过 getDerivedStateProps 的帮助来实现。
那 getDerivedStateProps 究竟是啥东西呢?首先 getDerivedStateProps 生命周期函数是一个静态函数,所以函数体内不能访问this。
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.translateX !== prevState.translateX) {
return {
translateX: nextProps.translateX,
};
}
return null;
}
这样的函数其实表达的意思很明确,我们在这个函数内部就好好的做个运算就行了,其他的骚操作就别再这做了。
比如很多开发者很喜欢在 componentWillMount 进行AJAX请求以获取数据,因为他们认为 componentWillMount在render 之前执行,早一点执行就早得到结果。但其实在 componentWillMount 中发起 AJAX 请求,不管多快得到结果,都赶不上首次 render 的速度,因此这种操作和可能会导致首屏无数据而导致白屏。
另外对于 React16 架构最大的变动就是 Fiber 了,在 Fiber 架构下启用了启用 async render 之后,render 之前的生命周期函数可能会被调用多次,如果在 componentWillMount 进行 AJAX 请求可能会导致无谓地多次调用AJAX。
其次在 React v16.3 刚发布这个函数的时候,getDerivedStateFromProps 这个生命周期函数,我在从它的名字来看的时候,还以为它主要是为了代替 componentWillReceiveProps 的,但进过了解后发现,这样说其实并不准确。因为 componentWillReceiveProps 只在因为父组件而引发的Updating过程中才会被调用。而getDerivedStateFromProps在Updating和Mounting过程中都会被调用。还需要注意的是,同样是 Updating 过程,如果是因为自身进行的 setState 或者 forceUpdate 所引发的渲染,getDerivedStateFromProps 也不会被调用。
这很容易引发一些问题,且让人难以理解这种差异,毕竟竟然可以在 updating 和 Mounting 过程中都可以调用,那么为什么在 setState 和 forceUpdate 发生时不会调用呢?且如果这样的话,那么在平时使用这个生命周期函数的时候,需不需要单独考虑不调用这个函数的时候需要怎么进行处理,诸如此类的问题还有不少。
也这是由于这个原因,React 团队很快就做出了调整,改正了这一点,让 getDerivedStateFromProps 无论是 Mounting 还是 Updating 还是自身组件的 setstate 或 forceUpdate 都会调用这个函数。这明显到简单多了,非常好理解:只要进行挂载或更新组件,都会调用 getDerivedStateFromProps 生命周期函数。
2.2 getSnapshotBeforeUpdate
除了 getDerivedStateFromProps 外,React v16.3还引入了一个新的声明周期函数getSnapshotBeforeUpdate。这函数会在render之后执行,而执行之时DOM元素还没有被更新,给了一个机会去获取DOM信息,计算得到一个snapshot,这个snapshot会作为componentDidUpdate的第三个参数传入。
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('#enter getSnapshotBeforeUpdate');
return 'foo';
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('#enter componentDidUpdate snapshot = ', snapshot);
}
上面这段代码可以看出来这个snapshot怎么个用法,snapshot咋看还以为是组件级别的某个“快照”,其实可以是任何值,到底怎么用完全看开发者自己,getSnapshotBeforeUpdate把snapshot返回,然后DOM改变,然后snapshot传递给componentDidUpdate。
官方给了一个例子,用getSnapshotBeforeUpdate来处理scroll,坦白说,我也想不出其他更常用更好懂的需要getSnapshotBeforeUpdate的例子,这个函数应该大部分开发者都用不上。(这是看程墨大佬的说法抄的,我还是没搞清楚这个函数有啥用。。。o(╥﹏╥)o)
其他
总的来说这些生命周期函数是 React 团队试图通过框架级别的 API 来约束或者说帮助开发者写出可维护性更佳的 JavaScript 代码。以前一些影响性能的操作,到现在React好像完全不能忍受了,逼着大家写好的代码,这其实挺不错的。
而这些生命周期函数的改动也之一要到React 17 才会进行实装,且那些要移除的生命周期也不会完全废弃,只需要加上UNSATE_的前缀还是可以用的。