React 函數組件是如何觸發更新的
- 1520字
- 8分鐘
- 2024-09-21
React 函數組件自從 Hooks 的引入以來,成為現代 React 應用開發的核心。相比類組件,函數組件不僅更簡潔,還擁有更強大的功能。不過,理解它背後的更新機制,尤其是從源碼層面的視角,能幫助我們更好地優化性能並避免不必要的重新渲染。本文將從 React 的底層源碼出發,深度解析函數組件的更新機制。
1. 函數組件更新的觸發條件
函數組件的更新主要通過以下幾種方式觸發:
- State 變化:通過
useState
的 setter 方法更新狀態。 - Props 變化:當父組件傳遞的 props 發生變化時,React 會重新渲染該組件。
- Context 變化:使用
useContext
獲取的上下文數據發生變化時,React 會重新渲染相關組件。
從源碼的角度看,React 會將組件的狀態、props 等數據存儲在內部的一個 Fiber 樹 中,當這些數據發生變化時,React 會進入調和(Reconciliation)階段,決定是否需要重新渲染組件。
2. Fiber 樹的核心作用
在 React 16 引入 Fiber 架構之後,所有組件(包括類組件和函數組件)的更新都被表示為一個 Fiber 節點。Fiber 樹的核心作用是將更新過程分為多個小任務來執行,而不是一次性完成,這樣 React 可以在渲染大任務時保持對用戶界面的響應性。
每個函數組件都會擁有一個對應的 Fiber 對象,該對象記錄了組件的 狀態(state)、props 以及 更新隊列(update queue)。當調用 setState
或 useState
的 setter 函數時,React 會將這個更新存入 Fiber 節點的更新隊列中,等待調度器來執行。
3. React 調度更新的過程
當函數組件的狀態或 props 發生變化時,React 會進入更新調度過程。核心流程如下:
3.1 setState 及 useState 的更新機制
當 useState
中的 setter 方法(如 setState
)被調用時,實際上會創建一個 更新對象,該對象包含了新的狀態值以及需要更新的組件引用。這個更新對象會被加入當前 Fiber 節點的更新隊列中,等待 React 調度。
1// 簡化後的 useState 實現2function useState(initialState) {3 const hook = getHook(); // 從當前 Fiber 節點獲取 hook 狀態4 if (!hook) {5 // 初始化 hook 狀態6 return mountState(initialState);7 }8 return updateState(hook);9}
每次更新,React 會根據 Fiber 樹中的每個 Fiber 節點執行更新邏輯。它通過 beginWork
函數檢查更新隊列,重新計算狀態值,並觸發組件的重新渲染。
3.2 Hooks 的存儲與重用
在函數組件的每次執行過程中,React 會通過一個內部鏈表來保存和重用 Hooks。每個 useState
、useEffect
調用都會在這條鏈表上創建或複用一個 hook 節點,從而存儲狀態值或副作用。
1function renderWithHooks(currentFiber, nextChildren) {2 currentlyRenderingFiber = currentFiber;3 currentHook = currentFiber.memoizedState; // 取出之前存儲的 hook 鏈表4 nextChildren = Component(props); // 重新執行函數組件5 return nextChildren;6}
每次函數組件更新時,React 會從頭開始執行函數體,但每個 Hook 都是按照順序保存的,因此可以依次取出對應的狀態和副作用,保持一致性。
4. 調和算法與虛擬 DOM 的工作原理
4.1 虛擬 DOM 的 Diff 算法
React 的更新機制依賴於 調和算法(Reconciliation) 來決定哪些部分需要更新。調和的核心步驟包括:
- 創建新的虛擬 DOM:每次組件更新時,React 會根據新的狀態和 props 生成一棵新的虛擬 DOM 樹。
- Diffing 階段:React 比較新舊兩棵虛擬 DOM 樹,通過 Diff 算法找出需要修改的部分。
- 更新實際 DOM:React 將差異最小化後,批量更新實際的 DOM。
Fiber 樹使 React 可以在工作單元(更新步驟)之間暫停和恢復,優化了長任務的執行過程,提高了應用的響應性。
4.2 更新優先級與調度
React 使用 優先級隊列 來控制更新的調度順序。每次狀態或 props 變化時,React 會將更新任務賦予一個優先級,根據任務的緊急程度決定何時執行。
- 高優先級更新(如用戶輸入事件)會立即執行。
- 低優先級更新(如動畫或後台數據加載)則會延後執行,保證 UI 的流暢性。
5. 函數組件性能優化:useMemo 與 useCallback
由於每次組件更新都會重新執行整個函數,因此使用 useMemo
和 useCallback
來緩存一些昂貴的計算或函數引用非常關鍵。
useMemo
:用於緩存計算結果,避免在每次渲染時重複計算。useCallback
:用於緩存函數,避免在子組件中每次都創建新的函數引用。
1const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);2
3const memoizedCallback = useCallback(() => {4 handleClick(a);5}, [a]);
這些 Hooks 通過緩存依賴不變的值或函數,減少了不必要的重新計算,從而提升了應用性能。
6. 函數組件的生命週期模擬
函數組件沒有類組件的生命週期方法,但通過 useEffect
,我們可以實現類似的生命週期功能:
componentDidMount
和componentDidUpdate
:使用useEffect
模擬,它在組件掛載或更新時執行。componentWillUnmount
:通過在useEffect
中返回清理函數來模擬。
1useEffect(() => {2 // 掛載或更新時執行3 return () => {4 // 卸載時執行5 };6}, [dependencies]); // 依賴數組控制執行時機
總結
React 函數組件的更新過程從狀態變化開始,通過 Fiber 樹和調和算法逐步完成對虛擬 DOM 的更新,再反映到實際 DOM 中。Hooks 的引入使函數組件更加簡潔,但也需要開發者更加關注性能優化,如合理使用 useMemo
、useCallback
和 React.memo
。
理解這些底層機制能幫助我們編寫更加高效的 React 應用,避免不必要的性能瓶頸。