变更的部分
react v16.3 终于出来了, 最大的变动莫过于生命周期去掉了以下三个
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
同时为了弥补失去上面三个周期的不足又加了两个
- static getDerivedStateFromProps
- getSnapshotBeforeUpdate
当然, 这个更替是缓慢的, 在整个 16 版本里都能无障碍的使用旧的三生命周期, 但值得注意的是, 旧的生命周期 (unsafe) 不能和新的生命周期同时出现在一个组件, 否则会报错 "你使用了一个不安全的生命周期".
为什么要改
旧的生命周期十分完整, 基本可以捕捉到组件更新的每一个 state/props/ref, 没有什从逻辑上的毛病.
但是架不住官方自己搞事情, react 打算在 17 版本推出新的 Async Rendering, 提出一种可被打断的生命周期, 而可以被打断的阶段正是实际 dom 挂载之前的虚拟 dom 构建阶段, 也就是要被去掉的三个生命周期.
生命周期一旦被打断, 下次恢复的时候又会再跑一次之前的生命周期, 因此 componentWillMount,componentWillReceiveProps, componentWillUpdate 都不能保证只在挂载 / 拿到 props / 状态变化的时候刷新一次了, 所以这三个方法被标记为不安全.
两个新生命周期
static getDerivedStateFromProps
触发时间: 在组件构建之后(虚拟 dom 之后, 实际 dom 挂载之前) , 以及每次获取新的 props 之后.
每次接收新的 props 之后都会返回一个对象作为新的 state, 返回 null 则说明不需要更新 state.
配合 componentDidUpdate, 可以覆盖 componentWillReceiveProps 的所有用法
- class Example extends React.Component {
- static getDerivedStateFromProps(nextProps, prevState) {
- // 没错, 这是一个 static
- }
- }
- getSnapshotBeforeUpdate
触发时间: update 发生的时候, 在 render 之后, 在组件 dom 渲染之前.
返回一个值, 作为 componentDidUpdate 的第三个参数.
配合 componentDidUpdate, 可以覆盖 componentWillUpdate 的所有用法.
- class Example extends React.Component {
- getSnapshotBeforeUpdate(prevProps, prevState) {
- // ...
- }
- }
建议用法总结
初始化 state - Initializing state
在 constructor 初始化 state 就可以了
请求数据 - Fetching external data
在 componentDidMount 请求异步加载的数据
有一种错觉, 在 componentWillMount 请求的数据在 render 就能拿到, 但其实 render 在 willMount 之后几乎是马上就被调用, 根本等不到数据回来, 同样需要 render 一次 "加载中" 的空数据状态, 所以在 didMount 去取数据几乎不会产生影响.
添加事件监听 - Adding event listeners (or subscriptions)
在 componentDidMount 中添加加事件监听
react 只能保证 componentDidMount-componentWillUnmount 成对出现, componentWillMount 可以被打断或调用多次, 因此无法保证事件监听能在 unmount 的时候被成功卸载, 可能会引起内存泄露
根据 props 更新 state - Updating state based on props
用 getDerivedStateFromProps(nextProps, prevState), 将传入的 props 更新到 state 上
用来代替 componentWillReceiveProps(nextProps, nextState),willReceiveProps 经常被误用, 导致了一些问题, 因此该方法将不被推荐使用.
getDerivedStateFromProps 是一个 static 方法, 意味着拿不到实例的 this, 所以想要在 setState 之前比对一下 props 有没有更新, 下面方法是不能用了
- if (this.props.currentRow !== nextProps.currentRow) {
- ...
- }
取而代之的是, 额外写一个 state 来记录上一个 props (` ^ ')
- if (nextProps.currentRow !== prevState.lastRow) {
- return {
- ...
- lastRow: nextProps.currentRow,
- };
- // 不更新 state
- return null
- }
为什么我们不给一个 prevProps 参数呢, 官方解释是, 一来 prevProps 第一次被调用的时候是 null, 每次更新都要判断耗性能, 二来如果大家都习惯了, 以后 react 不记录 prevProps 的话(啥), 可以省下不少内存
触发请求 - Invoking external callbacks
在生命周期中由于 state 的变化触发请求, 在 componentDidUpdate 中进行
为什么不在 componentWillUpdate 中的理由同上 2
props 更新引起的副作用 - Side effects on props change
props 更改引发的可视变化(副作用, 比如 log,ga), 在 componentDidUpdate 中处理
- // 在 didUpdate 中根据 props 更新的确很不适应
- // props 变了也是可以触发 update 的
- componentDidUpdate(prevProps, prevState) {
- if (this.props.isVisible !== prevProps.isVisible) {
- logVisibleChange(this.props.isVisible);
- }
- }
componentWillUpdate, componentWillReceiveProps 在一次更新中可能会被触发多次, 因此这种只希望触发一次的副作用应该放在保证只触发一次的 componentDidUpdate 中.
props 更新时重新请求 - Fetching external data when props change
传入新的 props 时重新异步取数据, getDerivedStateFromProps+ componentDidUpdate 替代 componentWillReceiveProps
- // old
- componentWillReceiveProps(nextProps) {
- if (nextProps.id !== this.props.id) {
- this.setState({
- externalData: null
- });
- this._loadAsyncData(nextProps.id);
- }
- }
- // new
- static getDerivedStateFromProps(nextProps, prevState) {
- // Store prevId in state so we can compare when props change.
- if (nextProps.id !== prevState.prevId) {
- return {
- externalData: null,
- prevId: nextProps.id,
- };
- }
- // No state update necessary
- return null;
- }
- componentDidUpdate(prevProps, prevState) {
- if (this.state.externalData === null) {
- this._loadAsyncData(this.props.id);
- }
- }
在更新前记录原来的 dom 节点属性 - Reading DOM properties before an update
在 upate 之前获取 dom 节点, getSnapshotBeforeUpdate(prevProps, prevState)代替 componentWillUpdate(nextProps, nextState)
getSnapshotBeforeUpdate 在 render 之后, 但在节点挂载前
componentDidUpdate(prevProps, prevState, snapshot)直接获得 getSnapshotBeforeUpdate 返回的 dom 属性值
生命周期功能替换一览
static getDerivedStateFromProps(nextProps, prevState) {
4. Updating state based on props
7. Fetching external data when props change
- }
- constructor() {
- 1. Initializing state
- }
- componentWillMount() {
- // 1. Initializing state
- // 2. Fetching external data
- // 3. Adding event listeners (or subscriptions)
- }
- componentDidMount() {
- 2. Fetching external data
- 3. Adding event listeners (or subscriptions)
- }
- componentWillReceiveProps() {
- // 4. Updating state based on props
- // 6. Side effects on props change
- // 7. Fetching external data when props change
- }
- shouldComponentUpdate() {
- }
- componentWillUpdate(nextProps, nextState) {
- // 5. Invoking external callbacks
- // 8. Reading DOM properties before an update
- }
- render() {
- }
- getSnapshotBeforeUpdate(prevProps, prevState) {
- 8. Reading DOM properties before an update
- }
- componentDidUpdate(prevProps, prevState, snapshot) {
5. Invoking external callbacks
6. Side effects on props change
- }
- componentWillUnmount() {
- }
最后
上面的内容基本就是结合我的一些经验半翻译半总结, 有不准确的地方欢迎指正.
对更多细节感兴趣的话可以去看官方文档, https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html
对 async-rendering 或者对 dan 小哥哥感兴趣的话可以去看看他在前端大会上的一段小演示: https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html
来源: https://juejin.im/post/5aca20c96fb9a028d700e1ce