自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

React Hooks 的原理,有的簡(jiǎn)單有的不簡(jiǎn)單

開(kāi)發(fā) 前端
React 支持 class 和 function 兩種形式的組件,class 支持 state 屬性和生命周期方法,而 function 組件也通過(guò) hooks api 實(shí)現(xiàn)了類(lèi)似的功能。

本文轉(zhuǎn)載自微信公眾號(hào)「神光的編程秘籍」,作者神說(shuō)要有光。轉(zhuǎn)載本文請(qǐng)聯(lián)系神光的編程秘籍公眾號(hào)。

React 是實(shí)現(xiàn)了組件的前端框架,它支持 class 和 function 兩種形式的組件。

class 組件是通過(guò)繼承模版類(lèi)(Component、PureComponent)的方式開(kāi)發(fā)新組件的,這是 class 本身的特性,它支持設(shè)置 state,會(huì)在 state 改變后重新渲染,可以重寫(xiě)一些父類(lèi)的方法,這些方法會(huì)在 React 組件渲染的不同階段調(diào)用,叫做生命周期函數(shù)。

function 組件不能做繼承,因?yàn)?function 本來(lái)就沒(méi)這個(gè)特性,所以是提供了一些 api 供函數(shù)使用,這些 api 會(huì)在內(nèi)部的一個(gè)數(shù)據(jù)結(jié)構(gòu)上掛載一些函數(shù)和值,并執(zhí)行相應(yīng)的邏輯,通過(guò)這種方式實(shí)現(xiàn)了 state 和類(lèi)似 class 組件的生命周期函數(shù)的功能,這種 api 就叫做 hooks。

hooks 掛載數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)叫做 fiber。

那什么是 fiber 呢?

我們知道,React 是通過(guò) jsx 來(lái)描述界面結(jié)構(gòu)的,會(huì)把 jsx 編譯成 render function,然后執(zhí)行 render function 產(chǎn)生 vdom:

在 v16 之前的 React 里,是直接遞歸遍歷 vdom,通過(guò) dom api 增刪改 dom 的方式來(lái)渲染的。但當(dāng) vdom 過(guò)大,頻繁調(diào)用 dom api 會(huì)比較耗時(shí),而且遞歸又不能打斷,所以有性能問(wèn)題。

后來(lái)就引入了 fiber 架構(gòu),先把 vdom 樹(shù)轉(zhuǎn)成 fiber 鏈表,然后再渲染 fiber。

vdom 轉(zhuǎn) fiber 的過(guò)程叫做 reconcile,是可打斷的,React 加入了 schedule 的機(jī)制在空閑時(shí)調(diào)度 reconcile,reconcile 的過(guò)程中會(huì)做 diff,打上增刪改的標(biāo)記(effectTag),并把對(duì)應(yīng)的 dom 創(chuàng)建好。然后就可以一次性把 fiber 渲染到 dom,也就是 commit。

這個(gè) schdule、reconcile、commit 的流程就是 fiber 架構(gòu)。當(dāng)然,對(duì)應(yīng)的這個(gè)數(shù)據(jù)結(jié)構(gòu)也叫 fiber。

hooks 就是通過(guò)把數(shù)據(jù)掛載到組件對(duì)應(yīng)的 fiber 節(jié)點(diǎn)上來(lái)實(shí)現(xiàn)的。

fiber 節(jié)點(diǎn)是一個(gè)對(duì)象,hooks 把數(shù)據(jù)掛載在哪個(gè)屬性呢?

我們可以 debugger 看下。

準(zhǔn)備這樣一個(gè)函數(shù)組件(代碼沒(méi)啥具體含義,就是為了調(diào)試 hooks):

function App() {
const [name, setName] = useState("guang");
useState('dong');

const handler = useCallback((evt) => {
setName('dong');
},[1]);

useEffect(() => {
console.log(1);
});

useRef(1);

useMemo(() => {
return 'guang and dong';
})

return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p onClick={handler}>
{name}
</p>
</header>
</div>
);
}

在函數(shù)打個(gè)斷點(diǎn),運(yùn)行到這個(gè)組件就會(huì)斷住。

我們看下調(diào)用棧:

上一個(gè)函數(shù)是 renderWithHooks,里面有個(gè) workingInProgress 的對(duì)象就是當(dāng)前的 fiber 節(jié)點(diǎn):

fiber 節(jié)點(diǎn)的 memorizedState 就是保存 hooks 數(shù)據(jù)的地方。

它是一個(gè)通過(guò) next 串聯(lián)的鏈表,展開(kāi)看一下:

鏈表一共六個(gè)元素,這和我們?cè)?function 組件寫(xiě)的 hooks 不就對(duì)上了么:

這就是 hooks 存取數(shù)據(jù)的地方,執(zhí)行的時(shí)候各自在自己的那個(gè) memorizedState 上存取數(shù)據(jù),完成各種邏輯,這就是 hooks 的原理。

這個(gè) memorizedState 鏈表是什么時(shí)候創(chuàng)建的呢?

好問(wèn)題,確實(shí)有個(gè)鏈表創(chuàng)建的過(guò)程,也就是 mountXxx。鏈表只需要?jiǎng)?chuàng)建一次,后面只需要 update。

所以第一次調(diào)用 useState 會(huì)執(zhí)行 mountState,后面再調(diào)用 useState 會(huì)執(zhí)行 updateState。

我們先集中精力把 mount 搞明白。

mountXxx 是創(chuàng)建 memorizedState 鏈表的過(guò)程,每個(gè) hooks api 都是這樣的:

它的實(shí)現(xiàn)也很容易想到,就是創(chuàng)建對(duì)應(yīng)的 memorizedState 對(duì)象,然后用 next 串聯(lián)起來(lái),也就是這段代碼:

當(dāng)然,創(chuàng)建這樣的數(shù)據(jù)結(jié)構(gòu)還是為了使用的,每種 hooks api 都有不同的使用這些 memorizedState 數(shù)據(jù)的邏輯,有的比較簡(jiǎn)單,比如 useRef、useCallback、useMemo,有的沒(méi)那么簡(jiǎn)單,比如 useState、useEffect。

為什么這么說(shuō)呢?我們看下它們的實(shí)現(xiàn)再說(shuō)吧。

先看這幾個(gè)簡(jiǎn)單的:

useRef

每個(gè) useXxx 的 hooks 都有 mountXxx 和 updateXxx 兩個(gè)階段,比如 ref 就是 mountRef 和 updateRef。

它的代碼是最簡(jiǎn)單的,只有這么幾行:

mountWorkInProgressHook 剛才我們看過(guò),就是創(chuàng)建并返回 memorizedState 鏈表的,同理,下面那個(gè) updateWorkInProgressHook 是更新的。

這些不用管,只要知道修改的是對(duì)應(yīng)的 memorizedState 鏈表中的元素就行了。

那 ref 在 memorizedState 上掛了什么呢?

可以看到是把傳進(jìn)來(lái)的 value 包裝了一個(gè)有 current 屬性的對(duì)象,凍結(jié)了一下,然后放在 memorizedState 屬性上。

后面 update 的時(shí)候,沒(méi)有做任何處理,直接返回這個(gè)對(duì)象。

所以,useRef 的功能就很容易猜到了:useRef 可以保存一個(gè)數(shù)據(jù)的引用,這個(gè)引用不可變。

這個(gè) hooks 是最簡(jiǎn)單的 hooks 了,給我們一個(gè)地方存數(shù)據(jù),我們也能輕易的實(shí)現(xiàn) useRef 這個(gè) hooks。

再來(lái)看個(gè)稍難點(diǎn)的:

useCallback

useCallback 在 memorizedState 上放了一個(gè)數(shù)組,第一個(gè)元素是傳入的回調(diào)函數(shù),第二個(gè)是傳入的 deps(對(duì) deps 做了下 undefined 的處理)。

更新的時(shí)候把之前的那個(gè) memorizedState 取出來(lái),和新傳入的 deps 做下對(duì)比,如果沒(méi)變,那就返回之前的回調(diào)函數(shù),也就是 prevState[0]。

如果變了,那就創(chuàng)建一個(gè)新的數(shù)組,第一個(gè)元素是傳入的回調(diào)函數(shù),第二個(gè)是傳入的 deps。

所以,useCallback 的功能也就呼之欲出了:useCallback 可以實(shí)現(xiàn)函數(shù)的緩存,如果 deps 沒(méi)變就不會(huì)創(chuàng)建新的,否則才會(huì)返回新傳入的函數(shù)。

這段邏輯其實(shí)也不難,就是多了個(gè)判斷邏輯。

再來(lái)看個(gè)和它差不多的:

useMemo

useMemo 也在 memorizedState 上放了個(gè)數(shù)組,第一個(gè)元素是傳入函數(shù)的執(zhí)行結(jié)果,第二個(gè)元素是 deps(對(duì) deps 為 undefined 的情況做了下處理)。

更新的時(shí)候也是取出之前的 memorizedState,和新傳入的 deps 做下對(duì)比,如果沒(méi)變,就返回之前的值,也就是 prevState[0]。

如果變了,創(chuàng)建一個(gè)新的數(shù)組放在 memorizedState,第一個(gè)元素是新傳入函數(shù)的執(zhí)行結(jié)果,第二個(gè)元素是 deps。

所以,useMemo 的功能大家也能猜出來(lái):useMemo 可以實(shí)現(xiàn)函數(shù)執(zhí)行結(jié)果的緩存,如果 deps 沒(méi)變,就直接拿之前的,否則才會(huì)執(zhí)行函數(shù)拿到最新結(jié)果返回。

實(shí)現(xiàn)邏輯和 useCallback 大同小異。

這三個(gè) hooks 難么?給大家一個(gè)對(duì)象來(lái)存儲(chǔ)數(shù)據(jù),大家都能寫(xiě)出來(lái),并不難。

因?yàn)樗鼈兪菦](méi)有別的依賴(lài)的,只是單純的緩存了下值而已。而像 useState、useEffect 這些就復(fù)雜一些了,主要是因?yàn)樾枰{(diào)度。

useState

state 改了之后是要觸發(fā)更新的調(diào)度的,React 有自己的調(diào)度邏輯,就是我們前面提到的 fiber 的 schedule,所以需要 dispatch 一個(gè) action。

(不展開(kāi)講,簡(jiǎn)單看一下)

這里詳細(xì)講要涉及到調(diào)度,就先不展開(kāi)了。

useEffect

同樣的,effect 傳入的函數(shù)也是被 React 所調(diào)度的,當(dāng)然,這里的調(diào)度不是 fiber 那個(gè)調(diào)度,而是單獨(dú)的 effect 調(diào)度:

(不展開(kāi)講,簡(jiǎn)單看一下)

hooks 負(fù)責(zé)把這些 effect 串聯(lián)成一個(gè) updateQueue 的鏈表,然后讓 React 去調(diào)度執(zhí)行。

所以說(shuō),useState、useEffect 這種 hooks 的實(shí)現(xiàn)是和 fiber 的空閑調(diào)度,effect 的調(diào)度結(jié)合比較緊密的,實(shí)現(xiàn)上更復(fù)雜了一些。

這里沒(méi)有展開(kāi)講,因?yàn)檫@篇文章的目的是把 hooks 的主要原理理清楚,不會(huì)太深入細(xì)節(jié)。

大家可能還聽(tīng)過(guò)自定義 hooks 的概念,那個(gè)是啥呢?

其實(shí)就是個(gè)函數(shù)調(diào)用,沒(méi)啥神奇的,我們可以把上面的 hooks 放到 xxx 函數(shù)里,然后在 function 組件里調(diào)用,對(duì)應(yīng)的 hook 鏈表是一樣的。

只不過(guò)一般我們會(huì)使用 React 提供的 eslint 插件,lint 了這些函數(shù)必須以 use 開(kāi)頭,但其實(shí)不用也沒(méi)事,它們和普通的函數(shù)封裝沒(méi)有任何區(qū)別。

總結(jié)

React 支持 class 和 function 兩種形式的組件,class 支持 state 屬性和生命周期方法,而 function 組件也通過(guò) hooks api 實(shí)現(xiàn)了類(lèi)似的功能。

fiber 架構(gòu)是 React 在 16 以后引入的,之前是 jsx -> render function -> vdom 然后直接遞歸渲染 vdom,現(xiàn)在則是多了一步 vdom 轉(zhuǎn) fiber 的 reconcile,在 reconcile 的過(guò)程中創(chuàng)建 dom 和做 diff 并打上增刪改的 effectTag,然后一次性 commit。這個(gè) reconcile 是可被打斷的,可以調(diào)度,也就是 fiber 的 schedule。

hooks 的實(shí)現(xiàn)就是基于 fiber 的,會(huì)在 fiber 節(jié)點(diǎn)上放一個(gè)鏈表,每個(gè)節(jié)點(diǎn)的 memorizedState 屬性上存放了對(duì)應(yīng)的數(shù)據(jù),然后不同的 hooks api 使用對(duì)應(yīng)的數(shù)據(jù)來(lái)完成不同的功能。

鏈表自然有個(gè)創(chuàng)建階段,也就是 mountXxx,之后就不需要再 mount 了,只需要 update。所以每個(gè) useXx 的實(shí)現(xiàn)其實(shí)都是分為了 mountXxx 和 updateXxx 兩部分的。

我們看了幾個(gè)簡(jiǎn)單的 hooks:useRef、useCallback、useMemo,它們只是對(duì)值做了緩存,邏輯比較純粹,沒(méi)有依賴(lài) React 的調(diào)度。而 useState 會(huì)觸發(fā) fiber 的 schedule,useEffect 也有自己的調(diào)度邏輯。實(shí)現(xiàn)上相對(duì)復(fù)雜一些,我們沒(méi)有繼續(xù)深入。

其實(shí)給我們一個(gè)對(duì)象來(lái)存取數(shù)據(jù),實(shí)現(xiàn) useRef、useCallback、useMemo 等 hooks 還是很簡(jiǎn)單的。對(duì)于需要調(diào)度的,則復(fù)雜一些。

對(duì)于自定義的 hooks,那個(gè)就是個(gè)函數(shù)調(diào)用,沒(méi)有任何區(qū)別。(lint 的規(guī)則不想遵守可以忽略)

所有 hooks api 都是基于 fiber 節(jié)點(diǎn)上的 memorizedState 鏈表來(lái)存取數(shù)據(jù)并完成各自的邏輯的。

所以,hooks 的原理簡(jiǎn)單么?只能說(shuō)有的簡(jiǎn)單,有的不簡(jiǎn)單。

責(zé)任編輯:武曉燕 來(lái)源: 神光的編程秘籍
相關(guān)推薦

2014-12-19 10:07:10

C

2012-06-26 09:40:14

部署開(kāi)發(fā)管理

2020-12-16 07:36:46

Redis字符串數(shù)據(jù)

2010-12-06 09:45:27

TechEd 2010

2009-07-20 10:06:47

虛擬化思杰操作系統(tǒng)

2010-01-19 10:10:28

2011-10-26 11:06:01

IBM朱近之華為

2010-03-30 14:06:35

2020-11-11 15:36:51

服務(wù)器

2013-06-04 17:10:00

Linux命令

2011-12-28 15:11:09

iOS推薦

2023-10-30 10:11:09

2014-02-24 14:45:23

XPath開(kāi)發(fā)工具

2014-08-21 10:14:09

APP界面設(shè)計(jì)移動(dòng)客戶(hù)端

2023-05-17 07:36:00

淺拷貝深拷貝對(duì)象

2012-10-18 13:26:03

多米音樂(lè)華為

2011-09-30 14:08:41

WiNetH3C

2015-01-12 10:28:58

移動(dòng)開(kāi)發(fā)框架LettuceMobile Fram

2015-01-12 12:10:11

移動(dòng)開(kāi)發(fā)框架LettuceMobile Fram

2020-12-21 10:55:41

Linux系統(tǒng)ls命令
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)