前言
在写这篇文章的时候,React 曾经出了 17.0.1 版本了,虽说还来探讨目前 React 新旧生命周期有点晚了,React 两个新生命周期尽管出了很久,但理论开发我却没有用过,因为 React 16 版本后咱们间接 React Hook 腾飞开发我的项目。
但对新旧生命周期的摸索,还是有助于咱们更好了解 React 团队一些思维和做法,于是明天就要回顾下这个问题和了解总结,尽管还是 React Hook 写法香,然而仍然要深究学习类组件的货色,理解 React 团队的一些思维与做法。
本文只探讨 React17 版本前的。
React 16 版本后做了什么
首先是给三个生命周期函数加上了 UNSAFE:
- UNSAFE_componentWillMount
- UNSAFE_componentWillReceiveProps
- UNSAFE_componentWillUpdate
这里并不是示意不平安的意思,它只是不倡议持续应用,并示意应用这些生命周期的代码可能在将来的 React 版本(目前 React17 还没有齐全破除)存在缺点,如 React Fiber 异步渲染的呈现。
同时新增了两个生命周期函数:
- getDerivedStateFromProps
- getSnapshotBeforeUpdate
UNSAFE_componentWillReceiveProps
UNSAFE_componentWillReceiveProps(nextProps)
先来说说这个函数,componentWillReceiveProps
该子组件办法并不是父组件 props 扭转才触发,官网答复是:
如果父组件导致组件从新渲染,即便 props 没有更改,也会调用此办法。如果只想解决更改,请确保进行以后值与变更值的比拟。
先来说说 React 为什么破除该函数,破除必定有它不好的中央。
componentWillReceiveProps
函数的个别应用场景是:
- 如果组件本身的某个 state 跟父组件传入的 props 密切相关的话,那么能够在该办法中判断前后两个 props 是否雷同,如果不同就依据 props 来更新组件本身的 state。
相似的业务需要比方:一个能够横向滑动的列表,以后高亮的 Tab 显然隶属于列表本身的状态,但很多状况下,业务需要会要求从内部跳转至列表时,依据传入的某个值,间接定位到某个 Tab。
但该办法毛病是会毁坏 state 数据的繁多数据源,导致组件状态变得不可预测,另一方面也会减少组件的重绘次数。
而在新版本中,官网将更新 state 与触发回调重新分配到了 getDerivedStateFromProps
与 componentDidUpdate
中,使得组件整体的更新逻辑更为清晰。
新生命周期办法 static getDerivedStateFromProps(props, state)
怎么用呢?
getDerivedStateFromProps 会在调用 render 办法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。
从函数名字就能够看出大略意思:应用 props 来派生 / 更新 state。这就是重点了,凡是你想应用该函数,都必须出于该目标,应用它才是正确且符合规范的。
跟 getDerivedStateFromProps
不同的是,它在挂载和更新阶段都会执行(componentWillReceiveProps
挂载阶段不会执行),因为更新 state 这种需要不仅在 props 更新时存在,在 props 初始化时也是存在的。
而且 getDerivedStateFromProps
在组件本身 state 更新也会执行而 componentWillReceiveProps
办法执行则取决于父组件的是否触发从新渲染,也能够看出 getDerivedStateFromProps
并不是 componentWillReceiveProps
办法的替代品.
引起咱们留神的是,这个生命周期办法是一个静态方法,静态方法不依赖组件实例而存在,故在该办法外部是无法访问 this 的。新版本生命周期办法能做的事件反而更少了,限度咱们只能依据 props 来派生 state,官网是基于什么考量呢?
因为无奈拿到组件实例的 this,这也导致咱们无奈在函数外部做 this.fetch()申请,或者不合理的 this.setState()操作导致可能的死循环或其余副作用。有没有发现,这都是不合理不标准的操作,但开发者们都有机会这样用。可如果加了个动态 static,间接强制咱们都无奈做了,也从而防止对生命周期的滥用。
React 官网也是通过该限度,尽量放弃生命周期行为的可控可预测,本源上帮忙了咱们防止不合理的编程形式,即一个 API 要放弃单一性,做一件事的理念。
如下例子:
// before
componentWillReceiveProps(nextProps) {if (nextProps.isLogin !== this.props.isLogin) {
this.setState({isLogin: nextProps.isLogin,});
}
if (nextProps.isLogin) {this.handleClose();
}
}
// after
static getDerivedStateFromProps(nextProps, prevState) {if (nextProps.isLogin !== prevState.isLogin) { // 被比照的 props 会被保留一份在 state 里
return {isLogin: nextProps.isLogin, // getDerivedStateFromProps 的返回值会主动 setState};
}
return null;
}
componentDidUpdate(prevProps, prevState) {if (!prevState.isLogin && this.props.isLogin) {this.handleClose();
}
}
UNSAVE_componentWillMount
UNSAFE_componentWillMount() 在挂载之前被调用。它在 render() 之前调用,因而在此办法中同步调用 setState() 不会触发额定渲染。
咱们应该防止在此办法中引入任何副作用或事件订阅,而是选用componentDidMount()
。
在 React 初学者刚接触的时候,可能有这样一个疑难:个别都是数据申请放在 componentDidMount
外面,但放在 componentWillMount
不是会更快获取数据吗?
因为了解是 componentWillMount
在 render 之前执行,早一点执行就早拿到申请后果;然而其实不论你申请多快,都赶不上首次 render,页面首次渲染仍旧处于没有获取异步数据的状态。
还有一个起因,componentWillMount
是服务端渲染惟一会调用的生命周期函数,如果你在此办法中申请数据,那么服务端渲染的时候,在服务端和客户端都会别离申请两次雷同的数据,这显然也咱们想看到的后果。
特地是有了 React Fiber,更有机会被调用屡次,故申请不应该放在 componentWillMount
中。
还有一个谬误的应用是在 componentWillMount
中订阅事件,并在 componentWillUnmount
中勾销掉相应的事件订阅。事实上只有调用 componentDidMount
后,React 能力保障稍后调用 componentWillUnmount
进行清理。而且服务端渲染时不会调用componentWillUnmount
,可能导致内存泄露。
还有人会将事件监听器(或订阅)增加到 componentWillMount
中,但这可能导致服务器渲染(永远不会调用 componentWillUnmount
)和异步渲染(在渲染实现之前可能被中断,导致不调用 componentWillUnmount
)的内存透露。
对于该函数,个别状况,如果我的项目有应用,则是通常把现有 componentWillMount
中的代码迁徙至 componentDidMount
即可。
UNSAFE_componentWillUpdate
当组件收到新的 props 或 state 时,会在渲染之前调用
UNSAFE_componentWillUpdate()
。应用此作为在更新产生之前执行筹备更新的机会。初始渲染不会调用此办法。
留神,不能在该办法中调用 this.setState();在 componentWillUpdate
返回之前,你也不应该执行任何其余操作(例如,dispatch Redux 的 action)触发对 React 组件的更新。
首先跟下面两个函数一样,该函数也产生在 render 之前,也存在一次更新被调用屡次的可能,从这一点上看就仍然不可取了。
其次,该办法常见的用法是在组件更新前,读取以后某个 DOM 元素的状态,并在 componentDidUpdate
中进行相应的解决。但 React 16 版本后有 suspense、异步渲染机制等等,render 过程能够被宰割成屡次实现,还能够被暂停甚至回溯,这导致 componentWillUpdate
和 componentDidUpdate
执行前后可能会距离很长时间,这导致 DOM 元素状态是不平安的,因为这时的值很有可能曾经生效了。而且足够使用户进行交互操作更改以后组件的状态,这样可能会导致难以追踪的 BUG。
为了解决这个问题,于是就有了新的生命周期函数:
getSnapshotBeforeUpdate(prevProps, prevState)
getSnapshotBeforeUpdate
在最近一次渲染输入(提交到 DOM 节点)之前调用。它使得组件能在产生更改之前从 DOM 中捕捉一些信息(例如,滚动地位)。此生命周期的任何返回值将作为第三个参数传入componentDidUpdate(prevProps, prevState, snapshot)
与 componentWillUpdate
不同,getSnapshotBeforeUpdate
会在最终的 render 之前被调用,也就是说在 getSnapshotBeforeUpdate
中读取到的 DOM 元素状态是能够保障与 componentDidUpdate
中统一的。
尽管 getSnapshotBeforeUpdate
不是一个静态方法,但咱们也应该尽量应用它去返回一个值。这个值会随后被传入到 componentDidUpdate
中,而后咱们就能够在 componentDidUpdate
中去更新组件的状态,而不是在 getSnapshotBeforeUpdate
中间接更新组件状态。防止了 componentWillUpdate
和 componentDidUpdate
配合应用时将组件长期的状态数据存在组件实例上节约内存,getSnapshotBeforeUpdate
返回的数据在 componentDidUpdate
中用完即被销毁,效率更高。
来看官网的一个例子:
class ScrollingList extends React.Component {constructor(props) {super(props);
this.listRef = React.createRef();}
getSnapshotBeforeUpdate(prevProps, prevState) {
// 咱们是否在 list 中增加新的 items?// 捕捉滚动地位以便咱们稍后调整滚动地位。if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// 如果咱们 snapshot 有值,阐明咱们刚刚增加了新的 items,// 调整滚动地位使得这些新 items 不会将旧的 items 推出视图。//(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}
如果我的项目中有用到 componentWillUpdate
的话,降级计划就是将现有的 componentWillUpdate
中的回调函数迁徙至 componentDidUpdate
。如果触发某些回调函数时须要用到 DOM 元素的状态,则将比照或计算的过程迁徙至 getSnapshotBeforeUpdate
,而后在 componentDidUpdate
中对立触发回调或更新状态。
除了这些,React 16 版本的仍然还有大改变,其中引人注目的就是 Fiber,之后我还会抽空写一篇对于 React Fiber 的文章,能够关注我的集体技术博文 Github 仓库,感觉不错的话欢送 star,给我一点激励持续写作吧~
参考:
- 为什么废除 react 生命周期函数
- React v16.3 版本新生命周期函数浅析及降级计划