React Hooks 原理

原文地址
React Hooks實(shí)例教學(xué)

目前,Hooks 應(yīng)該是 React 中最火的概念了,在閱讀這篇文章之前,希望你已經(jīng)了解了基本的 Hooks 用法。

在使用 Hooks 的時(shí)候,我們可能會(huì)有很多疑惑

  1. 為什么只能在函數(shù)最外層調(diào)用 Hook,不要在循環(huán)、條件判斷或者子函數(shù)中調(diào)用?
  2. 為什么 useEffect 第二個(gè)參數(shù)是空數(shù)組,就相當(dāng)于 ComponentDidMount ,只會(huì)執(zhí)行一次?
  3. 自定義的 Hook 是如何影響使用它的函數(shù)組件的?
  4. Capture Value 特性是如何產(chǎn)生的?
  5. ......

這篇文章我們不會(huì)講解 Hooks 的概念和用法,而是會(huì)帶你從零實(shí)現(xiàn)一個(gè) tiny hooks,知其然知其所以然。

useState

  1. 最簡(jiǎn)單的 useState 用法是這樣的:
    demo1: https://codesandbox.io/s/v0nqm309q3

    function Counter() {
      var [count, setCount] = useState(0);
    
      return (
        <div>
          <div>{count}</div>
          <Button onClick={() => { setCount(count + 1); }}>
            點(diǎn)擊
          </Button>
        </div>
      );
    }
    
  2. 基于 useState 的用法,我們嘗試著自己實(shí)現(xiàn)一個(gè) useState:
    demo2:https://codesandbox.io/s/myy5qvoxpp

    function useState(initialValue) {
      var state = initialValue;
      function setState(newState) {
        state = newState;
        render();
      }
      return [state, setState];
    }
    
  3. 這時(shí)我們發(fā)現(xiàn),點(diǎn)擊 Button 的時(shí)候,count 并不會(huì)變化,為什么呢?我們沒(méi)有存儲(chǔ) state,每次渲染 Counter 組件的時(shí)候,state 都是新重置的。

    自然我們就能想到,把 state 提取出來(lái),存在 useState 外面。
    demo3:https://codesandbox.io/s/q9wq6w5k3w

    var _state; // 把 state 存儲(chǔ)在外面
    
    function useState(initialValue) {
      _state = _state | initialValue; // 如果沒(méi)有 _state,說(shuō)明是第一次執(zhí)行,把 initialValue 復(fù)制給它
      function setState(newState) {
        _state = newState;
        render();
      }
      return [_state, setState];
    }
    

到目前為止,我們實(shí)現(xiàn)了一個(gè)可以工作的 useState,至少現(xiàn)在來(lái)看沒(méi)啥問(wèn)題。
接下來(lái),讓我們看看 useEffect 是怎么實(shí)現(xiàn)的。

useEffect

useEffect 是另外一個(gè)基礎(chǔ)的 Hook,用來(lái)處理副作用,最簡(jiǎn)單的用法是這樣的:
demo4:https://codesandbox.io/s/93jp55qyp4

 useEffect(() => {
    console.log(count);
 }, [count]);

我們知道 useEffect 有幾個(gè)特點(diǎn):

  1. 有兩個(gè)參數(shù) callback 和 dependencies 數(shù)組
  2. 如果 dependencies 不存在,那么 callback 每次 render 都會(huì)執(zhí)行
  3. 如果 dependencies 存在,只有當(dāng)它發(fā)生了變化, callback 才會(huì)執(zhí)行

我們來(lái)實(shí)現(xiàn)一個(gè) useEffect
demo5:https://codesandbox.io/s/3kv3zlvzl1

let _deps; // _deps 記錄 useEffect 上一次的 依賴

function useEffect(callback, depArray) {
  const hasNoDeps = !depArray; // 如果 dependencies 不存在
  const hasChangedDeps = _deps
    ? !depArray.every((el, i) => el === _deps[i]) // 兩次的 dependencies 是否完全相等
    : true;
  /* 如果 dependencies 不存在,或者 dependencies 有變化*/
  if (hasNoDeps || hasChangedDeps) {
    callback();
    _deps = depArray;
  }
}

到這里,我們又實(shí)現(xiàn)了一個(gè)可以工作的 useEffect,似乎沒(méi)有那么難。
此時(shí)我們應(yīng)該可以解答一個(gè)問(wèn)題:

Q:為什么第二個(gè)參數(shù)是空數(shù)組,相當(dāng)于 componentDidMount ?

A:因?yàn)橐蕾囈恢辈蛔兓?,callback 不會(huì)二次執(zhí)行。

Not Magic, just Arrays

到現(xiàn)在為止,我們已經(jīng)實(shí)現(xiàn)了可以工作的 useState 和 useEffect。但是有一個(gè)很大的問(wèn)題:它倆都只能使用一次,因?yàn)橹挥幸粋€(gè) _state 和 一個(gè) _deps。比如

const [count, setCount] = useState(0);
const [username, setUsername] = useState('fan');

count 和 username 永遠(yuǎn)是相等的,因?yàn)樗麄児灿昧艘粋€(gè) _state,并沒(méi)有地方能分別存儲(chǔ)兩個(gè)值。我們需要可以存儲(chǔ)多個(gè) _state 和 _deps。

如 《React hooks: not magic, just arrays》所寫,我們可以使用數(shù)組,來(lái)解決 Hooks 的復(fù)用問(wèn)題。
demo6:https://codesandbox.io/s/50ww35vkzl

代碼關(guān)鍵在于:

  1. 初次渲染的時(shí)候,按照 useState,useEffect 的順序,把 state,deps 等按順序塞到 memoizedState 數(shù)組中。
  2. 更新的時(shí)候,按照順序,從 memoizedState 中把上次記錄的值拿出來(lái)。
  3. 如果還是不清楚,可以看下面的圖。
let memoizedState = []; // hooks 存放在這個(gè)數(shù)組
let cursor = 0; // 當(dāng)前 memoizedState 下標(biāo)

function useState(initialValue) {
  memoizedState[cursor] = memoizedState[cursor] || initialValue;
  const currentCursor = cursor;
  function setState(newState) {
    memoizedState[currentCursor] = newState;
    render();
  }
  return [memoizedState[cursor++], setState]; // 返回當(dāng)前 state,并把 cursor 加 1
}

function useEffect(callback, depArray) {
  const hasNoDeps = !depArray;
  const deps = memoizedState[cursor];
  const hasChangedDeps = deps
    ? !depArray.every((el, i) => el === deps[i])
    : true;
  if (hasNoDeps || hasChangedDeps) {
    callback();
    memoizedState[cursor] = depArray;
  }
  cursor++;
}

我們用圖來(lái)描述 memoizedState 及 cursor 變化的過(guò)程。

1. 初始化

1

2. 初次渲染

2

3. 事件觸發(fā)

3

4. Re Render

4

到這里,我們實(shí)現(xiàn)了一個(gè)可以任意復(fù)用的 useState 和 useEffect。

同時(shí),也可以解答幾個(gè)問(wèn)題:

Q:為什么只能在函數(shù)最外層調(diào)用 Hook?為什么不要在循環(huán)、條件判斷或者子函數(shù)中調(diào)用。

A:memoizedState 數(shù)組是按 hook定義的順序來(lái)放置數(shù)據(jù)的,如果 hook 順序變化,memoizedState 并不會(huì)感知到。

Q:自定義的 Hook 是如何影響使用它的函數(shù)組件的?

A:共享同一個(gè) memoizedState,共享同一個(gè)順序。

Q:“Capture Value” 特性是如何產(chǎn)生的?

A:每一次 ReRender 的時(shí)候,都是重新去執(zhí)行函數(shù)組件了,對(duì)于之前已經(jīng)執(zhí)行過(guò)的函數(shù)組件,并不會(huì)做任何操作。

真正的 React 實(shí)現(xiàn)

雖然我們用數(shù)組基本實(shí)現(xiàn)了一個(gè)可用的 Hooks,了解了 Hooks 的原理,但在 React 中,實(shí)現(xiàn)方式卻有一些差異的。

  • React 中是通過(guò)類似單鏈表的形式來(lái)代替數(shù)組的。通過(guò) next 按順序串聯(lián)所有的 hook。

    type Hooks = {
        memoizedState: any, // 指向當(dāng)前渲染節(jié)點(diǎn) Fiber
      baseState: any, // 初始化 initialState, 已經(jīng)每次 dispatch 之后 newState
      baseUpdate: Update<any> | null,// 當(dāng)前需要更新的 Update ,每次更新完之后,會(huì)賦值上一個(gè) update,方便 react 在渲染錯(cuò)誤的邊緣,數(shù)據(jù)回溯
      queue: UpdateQueue<any> | null,// UpdateQueue 通過(guò)
      next: Hook | null, // link 到下一個(gè) hooks,通過(guò) next 串聯(lián)每一 hooks
    }
    
    type Effect = {
      tag: HookEffectTag, // effectTag 標(biāo)記當(dāng)前 hook 作用在 life-cycles 的哪一個(gè)階段
      create: () => mixed, // 初始化 callback
      destroy: (() => mixed) | null, // 卸載 callback
      deps: Array<mixed> | null,
      next: Effect, // 同上 
    };
    
  • memoizedState,cursor 是存在哪里的?如何和每個(gè)函數(shù)組件一一對(duì)應(yīng)的?

    我們知道,react 會(huì)生成一棵組件樹(或Fiber 單鏈表),樹中每個(gè)節(jié)點(diǎn)對(duì)應(yīng)了一個(gè)組件,hooks 的數(shù)據(jù)就作為組件的一個(gè)信息,存儲(chǔ)在這些節(jié)點(diǎn)上,伴隨組件一起出生,一起死亡。

5

更對(duì)閱讀:深入 React Hook 系統(tǒng)的原理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • React是現(xiàn)在最流行的前端框架之一,它的輕量化,組件化,單向數(shù)據(jù)流等特性把前端引入了一個(gè)新的高度,現(xiàn)在它又引入的...
    老鼠AI大米_Java全棧閱讀 5,853評(píng)論 0 26
  • Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他...
    Oldboyyyy閱讀 4,907評(píng)論 0 3
  • Effect Hook可以使得你在函數(shù)組件中執(zhí)行一些帶有副作用的方法。 上面這段代碼是基于上個(gè)state hook...
    xiaohesong閱讀 4,379評(píng)論 0 0
  • 之前我們介紹了使用hooks的原因,在開(kāi)始介紹api之前,現(xiàn)在我們先來(lái)整體的預(yù)覽下這些api。從上篇的介紹可以知道...
    xiaohesong閱讀 61,030評(píng)論 11 17
  • 遠(yuǎn)處,微白。 陽(yáng)光不烈,深藏于云層之后,嬌羞地留下靜謐的背影,略顯黯淡,卻捎來(lái)絲絲縷縷的清涼。微風(fēng)...
    Christine伊純閱讀 385評(píng)論 0 0

友情鏈接更多精彩內(nèi)容