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)橐蕾囈恢辈蛔兓诒瑁琧allback 不會(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》所寫(xiě)癌瘾,我們可以使用數(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ì)生成一棵組件樹(shù)(或Fiber 單鏈表)互例,樹(shù)中每個(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)系作者
  • 序言:七十年代末肩杈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子解寝,更是在濱河造成了極大的恐慌扩然,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件聋伦,死亡現(xiàn)場(chǎng)離奇詭異夫偶,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)觉增,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門兵拢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)逾礁,“玉大人说铃,你說(shuō)我怎么就攤上這事∴诼模” “怎么了腻扇?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)砾嫉。 經(jīng)常有香客問(wèn)我幼苛,道長(zhǎng),這世上最難降的妖魔是什么焕刮? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任舶沿,我火速辦了婚禮,結(jié)果婚禮上配并,老公的妹妹穿的比我還像新娘括荡。我一直安慰自己,他們只是感情好荐绝,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布一汽。 她就那樣靜靜地躺著避消,像睡著了一般低滩。 火紅的嫁衣襯著肌膚如雪召夹。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天恕沫,我揣著相機(jī)與錄音监憎,去河邊找鬼。 笑死婶溯,一個(gè)胖子當(dāng)著我的面吹牛鲸阔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播迄委,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼褐筛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了叙身?” 一聲冷哼從身側(cè)響起渔扎,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎信轿,沒(méi)想到半個(gè)月后晃痴,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡财忽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年倘核,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片即彪。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡紧唱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出祖凫,到底是詐尸還是另有隱情琼蚯,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布惠况,位于F島的核電站遭庶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏稠屠。R本人自食惡果不足惜峦睡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望权埠。 院中可真熱鬧榨了,春花似錦、人聲如沸攘蔽。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至转捕,卻和暖如春作岖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背五芝。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工痘儡, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人枢步。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓沉删,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親醉途。 傳聞我的和親對(duì)象是個(gè)殘疾皇子矾瑰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

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