輕鬆掌握React Hooks底層實現原理

因爲最近業務較忙,2020年搞懂React原理系列文章最終篇直到如今纔在業餘時間抽空完成。以前在公司內部已有過一次PPT形式的分享,但通過一段時間對hooks的深度使用,對其又有了更深一些瞭解,故本次加上新內容並以文章形式再分享一次。前端

持續一年閱讀React源碼和總結其核心原理,慢慢也有了一些心得:react

讀懂源碼只是第一步,弄懂其功能的代碼實現方式。而再進一步是完全搞懂其實現原理、思想,它經過什麼方式實現了什麼功能,帶來了什麼價值。git

無論它的底層代碼如何改寫,最終的目的都是爲了實現某個功能。只要咱們把其功能實現原理掌握,即可活學活用,結合業務讓業務開發效率更高,或圍繞業務作一些生產力工具。github

React使用當前最新版本:17.0.1redux

今年寫了一個「搞懂React源碼系列」,把React最核心的內容用最易懂的方式講清楚。2020年搞懂React源碼系列:api

少了React Fiber更新原理?那是由於國外大佬的一篇文章寫得太好,沒有必要再重複寫一次。或許明年能夠找個時間寫個簡明概要的React全部原理彙總文章。數組

本文將重點講useMemouseEffectuseState3個api,由於它們在平常開發中最經常使用。後面講其餘幾個api。本次主要描述每一個hook的功能和原理。緩存

基礎知識

任何一個hook的調用方式都是:前端框架

輸出 = hook函數(輸入)

必定會有輸入和hook函數和輸出。
而被調用的過程通常是2種:組件初始化和組件更新。框架

UseMemo實現原理

useMemo的功能是記憶某個結果,只有依賴項發生改變時才更新輸出結果。

輸出結果 = useMemo(計算函數,依賴項)

下方展現其在不一樣過程當中useMemo內部實現原理。

輸入 hook函數 輸出
計算函數,依賴項 useMemo 計算結果

組件初始化

  1. 執行計算函數,獲取計算結果
  2. 緩存結果結果和依賴項
  3. 返回計算結果

組件更新:

if (依賴項和已緩存依賴項相同) {
    返回已緩存計算結果
} else {
    執行計算函數,獲取新計算結果
    緩存新計算結果和新依賴項
    返回新計算結果
}
其中一個問題值得注意,依賴項是如何比較的?深比較或淺比較?由於依賴項通常是一個數組,而數組中的每一個元素是具體的依賴變量,那麼React是如何比較的?

翻看源碼,發現若兩個依賴項都是數組,則React會使用Object.is對其每個元素進行強比較。

Object.is('foo', 'foo');     // true
Object.is(window, window);   // true

Object.is('foo', 'bar');     // false
Object.is([], []);           // false

var foo = { a: 1 };
var bar = { a: 1 };
Object.is(foo, foo);         // true
Object.is(foo, bar);         // false

Object.is(null, null);       // true

// Special Cases
Object.is(0, -0);            // false
Object.is(-0, -0);           // true
Object.is(NaN, 0/0);         // true
轉念一想,其實就應這樣比較。

UseEffect實現原理

useEffect(建立函數,依賴項)

useEffect的主要功能是:

組件加載後執行建立函數,建立函數執行後會返回一個銷燬函數,在組件銷燬前執行。

若依賴項爲數組且不爲空,則依賴項改變時,會執行上一個銷燬函數和從新執行建立函數。

輸入 hook函數
建立函數,依賴項 useEffect

useEffect直接被調用的過程是組件初始化和組件更新,其銷燬函數被調用的過程是組件銷燬。

組件初始化

  1. 生成一個effect對象,包含建立函數
  2. 緩存effect和依賴項
  3. 當React進入提交階段,執行effect中的建立函數,獲取銷燬函數。若銷燬函數不爲空,則將其放入effect。

組件更新

  1. 生成一個effect對象, 包含建立函數
  2. 檢查已緩存effect中是否有銷燬函數,有的話則放入新effect對象
  3. 緩存effect
  4. 若依賴項和已緩存依賴項不一樣,則將hasEffect標記添加到effect,並緩存新依賴項
  5. 當React進入提交階段:
if (effect有hasEffect標記) {
    若effect中有銷燬函數,則先執行銷燬函數
    執行effect中的建立函數,獲取銷燬函數。若銷燬函數不爲空,則將其放入effect
}

組件銷燬

  1. 若effect中有銷燬函數,則執行。

UseState實現原理

useState的功能是設置一個狀態的初始值,並返回當前狀態和設置狀態的函數。

[狀態,設置狀態函數] = useState(初始狀態)
輸入 hook函數 輸出
初始狀態 useState 狀態,設置狀態函數

useState直接被調用的過程也是組件初始化和組件更新,其還有一個調用設置狀態函數的過程。

組件初始化

  1. 若初始狀態爲函數,則將函數執行結果設爲當前狀態。不然將初始狀態設爲當前狀態。
  2. 生成設置狀態函數
  3. 緩存當前狀態和設置狀態函數
  4. 返回當前狀態

組件更新

  1. 讀取緩存狀態和設置狀態函數
  2. 返回緩存狀態

執行設置狀態函數

  1. 更新緩存狀態
  2. 觸發React組件樹更新
  3. 在下一次組件更新時,將返回已被更新的緩存狀態

useReducer

useReducer的功能和原理與useState一致,區別在於useReducer使用函數管理狀態,使用派發動做指令函數做爲設置狀態函數。Reducer概念可參看redux。

[狀態,派發動做指令函數]=useReducer(reducer函數,初始狀態)

UseCallback實現原理

已緩存函數 = useCallback(待緩存函數,依賴項)

useCallback的功能就是useMemo記憶函數一個封裝,相比useMemo只是少套了一層函數:

已緩存函數 = useMemo( () => 待緩存函數, 依賴項)

不過React內部並無用useMemo直接實現useCallback,而是用一套相似useMemo的代碼實現。

UseRef實現原理

{current: 當前值} = useRef(初始當前值)

useRef的功能是生成一個對象,結構是:{current: 當前值}, 對象一旦初始化,不會由於組件更新而改變。

雖然對象初始化後不會因組件更新改變,但咱們能夠經過更改其current屬性,當前值就至關於一個組件的變量,相似class組件的實例屬性。

useRef最經常使用的場景莫過於設置組件的ref。

const container = useRef(null)
return <div ref={container}></div>

其實此處官網也有特別講,div上的ref屬性將觸發設置container.current爲dom對象。

但咱們也能夠把useRef做爲生成組件變量的方法靈活應用。

輸入 hook函數 輸出
初始當前值 useRef {current: 當前值}

組件初始化

  1. 生成對象: { current: 初始當前值 }
  2. 緩存對象
  3. 返回緩存對象

組件更新

  1. 獲取緩存對象
  2. 返回緩存對象

UseImperativeHandle

useImperativeHandle的功能被子組件使用,實現父組件調用子組件內部方法,通常與forwardRef一塊兒使用。

UseImperativeHandle實現原理與useEffect相似。

UseLayoutEffect

useLayoutEffect和useLayout的功能區別:

useLayoutEffect useLayout
渲染到屏幕前執行 渲染到屏幕後執行
useLayoutEffect(() => {
   // 組件初始化或更新在渲染到屏幕前執行
   return () => {
     // 1. 組件卸載前執行 2. 執行下一個effect前執行
   } 
}, )

在跨年前完成2020搞懂React原理系列文章最後一篇,也是爲了迎接即將到來的2021。

在2021年,新的系列即將啓航。不過在寫新系列前,下一篇將先寫微前端框架實現原理。

祝你們新年快樂!

相關文章
相關標籤/搜索