请解释 React 生命周期?

2022年11月16日

💎 加入 E+ 成長計畫 與超過 500+ 位軟體工程師一同在社群中成長,並且獲得更多的軟體工程學習資源

了解 React 生命周期不仅是学习 React 最基本的概念之一,也是面试经典问题。本文会介绍三大生命周期、以及在 Class Component 中常用的生命周期方法。

React 元件生命周期
React 元件生命周期
圖片來源:react-lifecycle-methods-diagram

React 元件生命周期分为三大: mountingupdatingunmounting。如果是使用 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 被重新渲染时,其生命周期将会依照下列的顺序呼叫这些方法:

Unmounting

当一个元件被从 DOM 中移除时,我们称之为 unmounting。这时将会呼叫:

Render 与 Commit

Mounting、Updating 和 Unmounting 这三个不同的生命周期中,分别会再细分 RenderCommit 这两个阶段。我们用这三个阶段来重新审视刚刚谈论的生命周期

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 的函式元件需要是纯函式?》一文)。

至于在 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 方法(详见此讨论)。

🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們