请解释 React 生命周期?
2022年11月16日
了解 React 生命周期不仅是学习 React 最基本的概念之一,也是面试经典问题。本文会介绍三大生命周期、以及在 Class Component 中常用的生命周期方法。
React 元件生命周期分为三大: mounting、updating、unmounting。如果是使用 class component ,我们可以透过 React 提供出来的一些方法,在元件的生命周期执行代码,以下提供一些常见的操作方法(注意,这些方法是 class component 的生命周期方法,如果是用 functional component 的话,概念就不一样)
生命周期:mounting、updating、unmounting
Mounting
当 React 首次渲染元件,并把渲染后的节点加入 DOM 中时,我们称这时为 mounting。当 React 把节点加入到 DOM 后,浏览器会渲染并绘制画面。在这个阶段,生命周期将会依照下列的顺序呼叫这些方法:
Updating
在 mounting 后,如果一个元件的 prop 或 state 有变化时,就会触发重新渲染。重新渲染后,React 会去比较虚拟 DOM 有哪些地方改变了,然后将改变的部分更新到实际的 DOM 上。这个阶段我们称它为 updating。当一个 component 被重新渲染时,其生命周期将会依照下列的顺序呼叫这些方法:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
Unmounting
当一个元件被从 DOM 中移除时,我们称之为 unmounting。这时将会呼叫:
Render 与 Commit
Mounting、Updating 和 Unmounting 这三个不同的生命周期中,分别会再细分 Render 与 Commit 这两个阶段。我们用这三个阶段来重新审视刚刚谈论的生命周期
Mounting
在 mounting 的 render 阶段,React 会呼叫根元件,并渲染该元件,如果该元件内有其他子元件,React 会递回地渲染。特别注意,为了能够实现 concurrent 的特点,render 阶段可能会被 React 暂停,中止或重新启动(例如有另一个元件的优先顺序比较重要,React 会暂停比较不重要的,先 render 比较重要的,之后再回来 render 比较不重要的),因为要确保暂停与重新启动不会影响 render 出的结果,React 的元件必须是纯函式(pure function),意即不能有副作用(side effect),且在每次呼叫时都会回传同样的结果。
这也是为什么,如果在 React 当中要使用到副作用,我们必须放在componentDidMount
这个生命周期方法(或useEffect
),在 render 时确保是纯函式,有副作用,就等 mounting 完成后,透过componentDidMount
来执行。 (关于纯函式的详细说明,可参考《[什么是纯函式(pure function)? 为什么 React 的函式元件需要是纯函式?
](https://www.explainthis.io/zh -hans/swe/react-pure-function)》一文)。
至于在 mounting 的 commit 阶段,React 会透过 appendChild()
这个 DOM API,把 render 出的结果,加入到 DOM 上面,然后实际显示到画面中。
Updating
在 updating 的 render 阶段,React 会呼叫触发更新的元件。这个过程是递归的,如果被呼叫的元件回传另一个元件,那 React 会再呼叫另一个元件,一直持续下去,直到没有元件被回传。
而到了 commit 阶段,React 会去比较 render 前与后的两个虚拟 DOM,然后把差异的部分,更新到实际的 DOM 上面。跟 mounting 时一样,在 updating 时,要确保没有副作用,如果要使用副作用,可以使用 componentDidUpdate()
这个生命周期方法 (或 useEffect
)。
Updating 时还有 pre-commit 阶段
上一段落谈到 render 与 commit 阶段。如果要更细节地讨论,在元件的 updating 时还会有一个 pre-commit 阶段。顾名思义这个阶段发生在实际 commit 之前 (实际把更新后的结果同步到 DOM 之前)。
在 updating 的 pre-commit 阶段,如果使用 class component,getSnapshotBeforeUpdate()
方法会被触发。这个方法让我们可以在 DOM 被真正改变之前先从其中抓取一些资讯 (例如:滚动的位置)。之所以需要这个方法,是因为在 updating 时,如果 render 阶段有异步操作,那么 render 阶段到 commit 阶段之间,可能不会是马上发生。换句话说,如果在这两个阶段间,网站的使用者做了某个操作(例如调整浏览器的视窗大小),那么在 render 阶段,透过componentWillUpdate
拿到的 DOM 资讯可能已经不是精确的。所以透过 getSnapshotBeforeUpdate
让开发者能拿到更精确的资讯。
除非是要做动画类的操作,getSnapshotBeforeUpdate()
几乎很少用到。另外,如果是使用 functional component,React 目前还没有提供可以模拟相对应的 Hook 方法(详见此讨论)。