在写这篇文章的时候,React 已经出了 17.0.1 版本了,虽然说还来讨论目前 React 新旧生命周期有点晚了,React 两个新生命周期虽然出了好久,但实际开发我却没有用过,由于 React 16 版本后咱们直接 React Hook 起飞开发项目。javascript
但对新旧生命周期的探索,仍是有助于咱们更好理解 React 团队一些思想和作法,因而今天就要回顾下这个问题和理解总结,虽然仍是 React Hook 写法香,可是依然要深究学习类组件的东西,了解 React 团队的一些思想与作法。html
本文只讨论 React17 版本前的。java
首先是给三个生命周期函数加上了 UNSAFE:react
这里并非表示不安全的意思,它只是不建议继续使用,并表示使用这些生命周期的代码可能在将来的 React 版本(目前 React17 尚未彻底废除)存在缺陷,如 React Fiber 异步渲染的出现。git
同时新增了两个生命周期函数:github
UNSAFE_componentWillReceiveProps(nextProps)
先来讲说这个函数,componentWillReceiveProps
编程
该子组件方法并非父组件 props 改变才触发,官方回答是:安全
若是父组件致使组件从新渲染,即便 props 没有更改,也会调用此方法。若是只想处理更改,请确保进行当前值与变动值的比较。
先来讲说 React 为何废除该函数,废除确定有它很差的地方。服务器
componentWillReceiveProps
函数的通常使用场景是:异步
但该方法缺点是会破坏 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(); } }
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
便可。
当组件收到新的 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,给我一点鼓励继续写做吧~
参考: