图解React组件生命周期
一、组件生命周期
每个react类组件都包含“生命周期方法”(function组件是没有生命周期方法的),你可以重写这些方法,以便于在运行过程中特定的阶段执行这些方法。在接下来的文章中,生命周期方法的书写会遵照如下规则,以方便大家理解。
1、加粗生命周期方法表示常用的生命周期方法,开发中会经常使用到。
2、未加粗的生命周期方法表示罕见的生命周期方法,开发中很少会用到。
3、画中横线的生命周期方法表示即将过期的方法,开发中应避免使用。
4、若一定要使用即将失效的生命周期方法,可以参考官方文档。
首先通过一幅图来看下react组建的全部生命周期(version: ^16.4,之前的版本会略有区别)。
二、挂载
当组件实例被创建并插入 DOM 中时,被称为组件挂载,其生命周期调用顺序如下:
-
constructor()
static getDerivedStateFromProps()
UNSAFE_componentWillMount()
render()
componentDidMount()
1. constructor(props)
在 React 组件挂载之前,会调用它的构造函数。在为 React.Component 子类实现构造函数时,应在其他语句之前前调用super(props)。否则,this.props在构造函数中可能会出现未定义的 bug。
通常,在 React 中,构造函数仅用于以下两种情况:
- 通过给this.state赋值对象来初始化内部 state。
- 为事件处理函数绑定实例
如果不初始化 state 且不进行方法绑定,则不需要为 React 组件实现构造函数。
2. static getDerivedStateFromProps(props, state)
getDerivedStateFromProps会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。此方法无权访问组件实例。如果你需要,可以通过提取组件 props 的纯函数及 class 之外的状态,在getDerivedStateFromProps()和其他 class 方法之间重用代码。
3. render()
render()方法是 class 组件中唯一必须实现的方法。该函数应该为纯函数,这意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。
如需与浏览器进行交互,请在componentDidMount()或其他生命周期方法中执行你的操作。保持render()为纯函数,可以使组件更容易思考。
4. componentDidMount()
componentDidMount()会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。
这个方法是比较适合添加订阅的地方。如果添加了订阅,请不要忘记在componentWillUnmount()里取消订阅。
三、更新
当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:
-
static getDerivedStateFromProps()
shouldComponentUpdate()
UNSAFE_componentWillReceiveProps()
UNSAFE_componentWillUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
1、shouldComponentUpdate(nextProps, nextState)
该方法是控制组件是否应该被更新。如果方法返回false则后续的生命周期方法将全部被跳过,该方法会接收nextProps 和nextState,可以通过比较this.props 和nextProps、this.state和nextState来决定是否要重新渲染组件,来对组件进行性能优化。你应该考虑使用内置的 PureComponent 组件,而不是手动编写 shouldComponentUpdate()
官网中不建议在 shouldComponentUpdate() 中进行深层比较或使用 jsON.stringify()。这样非常影响效率,且会损害性能。
2、getSnapshotBeforeUpdate(prevProps, prevState)
getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()。
3、componentDidUpdate(prevProps, prevState, snapshot)
componentDidUpdate() 会在更新后会被立即调用。首次渲染不会执行此方法。
当组件更新后,可以在此处对 DOM 进行操作。如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。(例如,当 props 未发生变化时,则不会执行网络请求)
componentDidUpdate(prevProps) {
// 典型用法(不要忘记比较 props):
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
你也可以在 componentDidUpdate() 中直接调用 setState(),但请注意它必须被包裹在一个条件语句里,正如上述的例子那样进行处理,否则会导致死循环。
四、卸载
componentWillUnmount()
componentWillUnmount()会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在componentDidMount()中创建的订阅等。
五、错误处理
static getDerivedStateFromError()
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染可以显示降级 UI
return { hasError: true };
}
此生命周期会在后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state,你可以通过判断state中的某个字段,来显示降级UI
componentDidCatch()
此生命周期在后代组件抛出错误后被调用,可以用于日志记录。 它接收两个参数:
- error—— 抛出的错误。
- info—— 带有componentStack key 的对象
componentDidCatch(error, info) {
logToMyService(info.componentStack);
}