因爲最近業務較忙,2020年搞懂React原理系列文章最終篇直到如今纔在業餘時間抽空完成。以前在公司內部已有過一次PPT形式的分享,但通過一段時間對hooks的深度使用,對其又有了更深一些瞭解,故本次加上新內容並以文章形式再分享一次。前端
持續一年閱讀React源碼和總結其核心原理,慢慢也有了一些心得:react
讀懂源碼只是第一步,弄懂其功能的代碼實現方式。而再進一步是完全搞懂其實現原理、思想,它經過什麼方式實現了什麼功能,帶來了什麼價值。git
無論它的底層代碼如何改寫,最終的目的都是爲了實現某個功能。只要咱們把其功能實現原理掌握,即可活學活用,結合業務讓業務開發效率更高,或圍繞業務作一些生產力工具。github
React使用當前最新版本:
17.0.1
redux今年寫了一個「搞懂React源碼系列」,把React最核心的內容用最易懂的方式講清楚。2020年搞懂React源碼系列:api
- React Diff原理
- React 調度原理
- 搭建閱讀React源碼環境-支持React全部版本斷點調試細分文件
- (當前)React Hooks原理
少了React Fiber更新原理?那是由於國外大佬的一篇文章寫得太好,沒有必要再重複寫一次。或許明年能夠找個時間寫個簡明概要的React全部原理彙總文章。數組
本文將重點講useMemo
、useEffect
和useState
3個api,由於它們在平常開發中最經常使用。後面講其餘幾個api。本次主要描述每一個hook的功能和原理。緩存
任何一個hook的調用方式都是:前端框架
輸出 = hook函數(輸入)
必定會有輸入和hook函數和輸出。
而被調用的過程通常是2種:組件初始化和組件更新。框架
useMemo的功能是記憶某個結果,只有依賴項發生改變時才更新輸出結果。
輸出結果 = useMemo(計算函數,依賴項)
下方展現其在不一樣過程當中useMemo內部實現原理。
輸入 | hook函數 | 輸出 |
---|---|---|
計算函數,依賴項 | useMemo | 計算結果 |
組件初始化:
組件更新:
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的主要功能是:
組件加載後執行建立函數,建立函數執行後會返回一個銷燬函數,在組件銷燬前執行。
若依賴項爲數組且不爲空,則依賴項改變時,會執行上一個銷燬函數和從新執行建立函數。
輸入 | hook函數 |
---|---|
建立函數,依賴項 | useEffect |
useEffect直接被調用的過程是組件初始化和組件更新,其銷燬函數被調用的過程是組件銷燬。
組件初始化:
組件更新:
if (effect有hasEffect標記) { 若effect中有銷燬函數,則先執行銷燬函數 執行effect中的建立函數,獲取銷燬函數。若銷燬函數不爲空,則將其放入effect }
組件銷燬:
useState的功能是設置一個狀態的初始值,並返回當前狀態和設置狀態的函數。
[狀態,設置狀態函數] = useState(初始狀態)
輸入 | hook函數 | 輸出 |
---|---|---|
初始狀態 | useState | 狀態,設置狀態函數 |
useState直接被調用的過程也是組件初始化和組件更新,其還有一個調用設置狀態函數的過程。
組件初始化:
組件更新:
執行設置狀態函數:
useReducer的功能和原理與useState一致,區別在於useReducer使用函數管理狀態,使用派發動做指令函數做爲設置狀態函數。Reducer概念可參看redux。
[狀態,派發動做指令函數]=useReducer(reducer函數,初始狀態)
已緩存函數 = useCallback(待緩存函數,依賴項)
useCallback的功能就是useMemo記憶函數一個封裝,相比useMemo只是少套了一層函數:
已緩存函數 = useMemo( () => 待緩存函數, 依賴項)
不過React內部並無用useMemo直接實現useCallback,而是用一套相似useMemo的代碼實現。
{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: 當前值} |
組件初始化:
組件更新:
useImperativeHandle的功能被子組件使用,實現父組件調用子組件內部方法,通常與forwardRef一塊兒使用。
UseImperativeHandle實現原理與useEffect相似。
useLayoutEffect和useLayout的功能區別:
useLayoutEffect | useLayout |
---|---|
渲染到屏幕前執行 | 渲染到屏幕後執行 |
useLayoutEffect(() => { // 組件初始化或更新在渲染到屏幕前執行 return () => { // 1. 組件卸載前執行 2. 執行下一個effect前執行 } }, )
在跨年前完成2020搞懂React原理系列文章最後一篇,也是爲了迎接即將到來的2021。
在2021年,新的系列即將啓航。不過在寫新系列前,下一篇將先寫微前端框架實現原理。
祝你們新年快樂!