概念
生命周期是一个抽象的概念,能让开发者产生联想记忆的往往是那些函数,比如 componentDidMount、componentWilMount 等等。然而这些函数并不是它的生命周期,只是在生命周期中按顺序被调用的函数。挂载 -> 更新 -> 卸载这一 React 组件完整的流程,才是生命周期。
流程梳理
挂载阶段:
挂载阶段是指组件从初始化到完成加载的过程。
-
constructor
初始化 state 与绑定函数
-
getDerivedStateFromProps
使组件在 props 变化时更新 state
触发时机:
-
当 props 被传入时
-
state 发生变化时
-
forceUpdate 被调用时
最常见的一个错误是认为只有 props 发生变化时,getDerivedStateFromProps 才会被调用,而实际上只要父级组件重新渲染时,getDerivedStateFromProps 就会被调用。所以是外部参数,也就是 props 传入时就会发生变化。
-
-
UNSAFE_componentWillMount
也就是 componentWillMount,本来用于组件即将加载前做某些操作,但目前被标记为弃用。因为在 React 的异步渲染机制下,该方法可能会被多次调用。
一个常见的错误是 componentWillMount 跟服务器端同构渲染的时候,如果在该函数里面发起网络请求,拉取数据,那么会在服务器端与客户端分别执行一次。所以更推荐在 componentDidMount 中完成数据拉取操作。
-
render
render 函数返回的 JSX 结构,用于描述具体的渲染内容。但切记,render 函数并没有真正的去渲染组件,渲染是依靠 React 操作 JSX 描述结构来完成的。还有一点需要注意,render 函数应该是一个纯函数,不应该在里面产生副作用,比如调用 setState 或者绑定事件。
那为什么不能 setState 呢?因为 render 函数在每次渲染时都会被调用,而 setState 会触发渲染,就会造成死循环。
那又为什么不能绑定事件呢?因为容易被频繁调用注册。
-
componentDidMount
componentDidMount 主要用于组件加载完成时做某些操作,比如发起网络请求或者绑定事件,该函数是接着 render 之后调用的。但 componentDidMount 一定是在真实 DOM 绘制完成之后调用吗?在浏览器端,我们可以这么认为。
但在其他场景下,尤其是 React Native 场景下,componentDidMount 并不意味着真实的界面已绘制完毕。由于机器的性能所限,视图可能还在绘制中。
更新阶段:
更新阶段是指外部 props 传入,或者 state 发生变化时的阶段。
-
UNSAFE_componentWillReceiveProps
该函数已被标记弃用,因为其功能可被函数 getDerivedStateFromProps 所替代。 另外,当 getDerivedStateFromProps 存在时 UNSAFE_componentWillReceiveProps 不会被调用
-
getDerivedStateFromProps
同挂载阶段的表现一致。
-
shouldComponentUpdate
该方法通过返回 true 或者 false 来确定是否需要触发新的渲染。因为渲染触发最后一道关卡,所以也是性能优化的必争之地。通过添加判断条件来阻止不必要的渲染。
React 官方提供了一个通用的优化方案,也就是 PureComponent。PureComponent 的核心原理就是默认实现了 shouldComponentUpdate 函数,在这个函数中对 props 和 state 进行浅比较,用来判断是否触发更新。
-
UNSAFE_componentWillUpdate
同样已废弃,因为后续的 React 异步渲染设计中,可能会出现组件暂停更新渲染的情况。
-
render
同挂载阶段的表现一致。
-
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate 方法是配合 React 新的异步渲染的机制,在 DOM 更新发生前被调用,返回值将作为 componentDidUpdate 的第三个参数。
// 官方示例: class ScrollingList extends React.Component { constructor(props) { super(props); this.listRef = React.createRef(); } getSnapshotBeforeUpdate(prevProps, prevState) { // Are we adding new items to the list? // Capture the scroll position so we can adjust scroll later. if (prevProps.list.length < this.props.list.length) { const list = this.listRef.current; return list.scrollHeight - list.scrollTop; } return null; } componentDidUpdate(prevProps, prevState, snapshot) { // If we have a snapshot value, we've just added new items. // Adjust scroll so these new items don't push the old ones out of view. // (snapshot here is the value returned from getSnapshotBeforeUpdate) if (snapshot !== null) { const list = this.listRef.current; list.scrollTop = list.scrollHeight - snapshot; } } render() { return ( <div ref={this.listRef}>{/_ ...contents... _/}</div> ); } -
componentDidUpdate
正如上面的案例,getSnapshotBeforeUpdate 的返回值会作为 componentDidUpdate 的第三个参数使用。
componentDidUpdate 中可以使用 setState,会触发重渲染,但一定要小心使用,避免死循环。
卸载阶段:
卸载阶段是指组件卸载到销毁的阶段。
-
componentWillUnmount
该函数主要用于执行清理工作。
一个比较常见的 Bug 就是忘记在 componentWillUnmount 中取消定时器,导致定时操作依然在组件销毁后不停地执行。所以一定要在该阶段解除事件绑定,取消定时器。