转载自掘金网络,原文链接:https://juejin.im/post/5df648836fb9a016526eba01?utm_source=gold_browser_extension
旧版生命周期
- 初始化的时候不会把赋值算作更新,所以不会执行更新阶段
1 | import React, { Component } from 'react' |
洋葱模型
新版生命周期
static getDerivedStateFromProps
static getDerivedStateFromProps(nextProps,prevState)
:接收父组件传递过来的props
和组件之前的状态,返回一个对象来更新state
或者返回null
来表示接收到的props
没有变化,不需要更新state
该生命周期钩子的作用: 将父组件传递过来的
props
映射 到子组件的state
上面,这样组件内部就不用再通过this.props.xxx
获取属性值了,统一通过this.state.xxx
获取。映射就相当于拷贝了一份父组件传过来的props
,作为子组件自己的状态。注意:子组件通过setState
更新自身状态时,不会改变父组件的props
配合
componentDidUpdate
,可以覆盖componentWillReceiveProps
的所有用法该生命周期钩子触发的时机:
- 在 React 16.3.0 版本中:在组件实例化、接收到新的
props
时会被调用 - 在 React 16.4.0 版本中:在组件实例化、接收到新的
props
、组件状态更新时会被调用 - 在线 demo —— 测试 16.3.0 和 16.4.0 版本中,该生命周期钩子什么情况下会被触发
- 在 React 16.3.0 版本中:在组件实例化、接收到新的
使用:
- 注意:派生状态时,不需要把组件自身的状态也设置进去
1 | import React from "react"; |
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps, prevState)
:接收父组件传递过来的props
和组件之前的状态,此生命周期钩子必须有返回值,返回值将作为第三个参数传递给componentDidUpdate
。必须和componentDidUpdate
一起使用,否则会报错- 该生命周期钩子触发的时机 :被调用于
render
之后、更新DOM
和refs
之前 - 该生命周期钩子的作用: 它能让你在组件更新
DOM
和refs
之前,从DOM
中捕获一些信息(例如滚动位置) - 配合
componentDidUpdate
, 可以覆盖componentWillUpdate
的所有用法 - 在线 demo:每次组件更新时,都去获取之前的滚动位置,让组件保持在之前的滚动位置
1 | import React, { Component } from "react"; |
版本迁移
componentWillMount
,componentWillReceiveProps
,componentWillUpdate
这三个生命周期因为经常会被误解和滥用,所以被称为 不安全(不是指安全性,而是表示使用这些生命周期的代码,有可能在未来的 React 版本中存在缺陷,可能会影响未来的异步渲染) 的生命周期。- React 16.3 版本:为不安全的生命周期引入别名
UNSAFE_componentWillMount
,UNSAFE_componentWillReceiveProps
和UNSAFE_componentWillUpdate
。(旧的生命周期名称和新的别名都可以在此版本中使用) - React 16.3 之后的版本:为
componentWillMount
,componentWillReceiveProps
和componentWillUpdate
启用弃用警告。(旧的生命周期名称和新的别名都可以在此版本中使用,但旧名称会记录DEV模式警告) - React 17.0 版本: 推出新的渲染方式——异步渲染( Async Rendering),提出一种可被打断的生命周期,而可以被打断的阶段正是实际
dom
挂载之前的虚拟dom
构建阶段,也就是要被去掉的三个生命周期componentWillMount
,componentWillReceiveProps
和componentWillUpdate
。(从这个版本开始,只有新的“UNSAFE_”生命周期名称将起作用)
常见问题
当外部的 props
改变时,如何再次执行请求数据、更改状态等操作
使用 componentWillReceiveProps
1 | class ExampleComponent extends React.Component { |
使用 getDerivedStateFromProps
+ componentDidUpdate
加载数据
1 | class ExampleComponent extends React.Component { |
使用 getDerivedStateFromProps
更改状态
1 | import React from "react"; |
只用 componentDidUpdate
的写法
- 不一定要使用
getDerivedStateFromProps
或者componentWillReceiveProps
- 在线 demo
1 | import React from "react"; |
使用 key 的写法
- 通过改变
key
,来重新初始化组件 在线 demo - 这听起来很慢,但是这点的性能是可以忽略的。如果在组件树的更新上有很重的逻辑,这样反而会更快,因为省略了子组件的
diff
- React 官方建议的模式
- 我觉得这种写法,非常适合:当你调用同事写的业务 UI 组件时,如果他没有考虑到组件内部状态需要跟随外部
props
的更改的情况(恨不得上去就给他个膝盖重锤 😂😂😂),可以使用key
来快速实现
1 | class ExampleComponent extends React.Component { |
getDerivedStateFromProps
是一个静态方法,而组件实例无法继承静态方法,所以该生命周期钩子内部无法通过使用 this
获取组件实例的属性/方法。
- 有些情况下,我们需要对父组件传递过来的数据进行过滤/筛选等操作,而这些操作一般都会放在一个单独的函数中(单一原则),然后将该生命周期钩子获取到的
props
传递进这些方法中进行处理。- 如果选择把这些方法放在
class
组件上,那么这些方法得申明成静态方法,然后在该生命周期钩子中通过className.xxx
调用这些方法。
- 如果选择把这些方法放在
1 | class AAA extends React.Component { |
- 或者把这些方法放在
class
组件外面,就不用申明成静态方法,在该生命周期钩子中直接调用这些方法。
1 | function filterFn(data){ |
- 在使用以上两种方法时,我个人认为的一个缺点:如果这些方法比较复杂,内部还调用了别的函数,此时,要么所有的处理函数都申明成静态方法,要么所有的方法都提到组件外部去,并且需要一层层的往下传递
props
值。无法像组件实例的方法一样,可以在每个组件实例方法内,通过this.props.xxx / this.state.xxx
访问属性,会比较麻烦。 - 还有一种方法: 结合
componentDidUpdate
使用 在线 demo
1 | import React from "react"; |
使用 getDerivedStateFromProps
派生状态时,不需要把组件自身的状态也设置进去
1 | class AAA extends React.Component { |
如果 setState
更新的值不变,那么还会触发这些生命周期钩子吗?
- 哪怕每次都设置同样的值,还是会触发更新
1 | import React, {Component} from 'react' |
不要在 componentWillMount
中添加事件监听
- 在
componentDidMount
中添加事件监听 componentWillMount
可以被打断或调用多次,因此无法保证事件监听能在 unmount 的时候被成功卸载,可能会引起内存泄露
由于 React 未来的版本中推出了异步渲染,在 dom
被挂载之前的阶段都可以被打断重来,导致 componentWillMount
、componentWillUpdate
、componentWillReceiveProps
在一次更新中可能会被触发多次,因此那些只希望触发一次的副作用应该放在 componentDidUpdate
中
- 这也就是为什么要把异步请求放在
componentDidMount
中,而不是放在componentWillMount
中的原因,为了向后兼容