[FE] React Hook: useState & setState

背景

React 16.8 引入了 Hook 的概念捻爷,一直以來都沒時間仔細(xì)研究它的實(shí)現(xiàn)方式雹顺,
本文從一個最簡單的例子出發(fā)弊仪,記錄了 react@16.13.1 的 useState 源碼邏輯。

概覽

示例代碼:github: debug-react

function App() {
  debugger;
  const [state, setState] = useState(0);
  debugger;

  const increment = () => {
    debugger;
    setState(s => {
      debugger;
      return s + 1;
    });
    debugger;
    setState(s => {
      debugger;
      return s + 2;
    });
    debugger;
  };

  debugger;
  return <button onClick={increment}>{state}</button>;
}

以上代碼中龄毡,使用 useState 并設(shè)置初始值為 0卦方,先完成了首次渲染。
然后俩檬,點(diǎn)擊頁面中的按鈕,觸發(fā) increment 中的 setState 對頁面進(jìn)行更新碾盟。

精簡版的業(yè)務(wù)邏輯如下棚辽,


image

標(biāo)紅的是首次渲染與更新不同的邏輯,留意以下幾個函數(shù)冰肴,
(1)首次渲染:mountIndeterminateComponent屈藐,mountStatecreateFiberFromElement
(2)更新:setState熙尉,updateFunctionComponent联逻,updateStateuseFiber

首次渲染

ReactDOM.render 觸發(fā)检痰,調(diào)用了組件的裝載方法 mountIndeterminateComponent包归,
接著就執(zhí)行組件(函數(shù) App)內(nèi)部的邏輯了。

App 內(nèi)部第一行就是 useState铅歼,
它會創(chuàng)建了一個 hook公壤,然后把它掛載到 fiber 上,
每個 hook 還包含了一個 queue椎椰。

fiber hook quque 的關(guān)系如下厦幅,

fiber: {  // Fiber
  // 節(jié)點(diǎn)相關(guān)
  sibling, child, return, index, 

  // 狀態(tài)相關(guān)
  memoizedState: {  // Hook
    memorizedState, baseState, baseQueue, next,
    queue: {  // UpdateQueue
      pending, dispatch, lastRenderedReducer, lastRenderedState,
    },
  },
  updateQueue, 
  
  // 渲染相關(guān)
  effectTag, nextEffect, alternate,
}

fiber 構(gòu)成了一棵樹,通過 sibling, child, return, index 相連俭识。
多個 hook 構(gòu)成一個循環(huán)鏈表慨削,通過 next 相連。

建好這些數(shù)據(jù)結(jié)構(gòu)之后套媚,useState 就返回了缚态,

return [hook.memoizedState, dispatch];

它返回了兩個值,一個是 hook.memoizedState 表示 hook 的當(dāng)前狀態(tài)堤瘤,
在首次渲染時玫芦,這個狀態(tài)就是初始值 0
另一個是 dispatch 函數(shù)本辐,也就是后文要用的 setState桥帆。

useState 及外層的 App 組件函數(shù)執(zhí)行完后,
mountIndeterminateComponent 會接著調(diào)用 reconcileChildren 創(chuàng)建新的 fiber慎皱。

fiber 整棵樹都更新完之后老虫,
就執(zhí)行 commitRoot 一次性的渲染到頁面上。

更新

更新邏輯在渲染到頁面之前茫多,分為兩步
(1)響應(yīng)事件
(2)更新狀態(tài)

(1)響應(yīng)事件

點(diǎn)擊頁面上的按鈕祈匙,React 會響應(yīng)綁定到 button 上的 onClick 事件,
從而調(diào)用了 increment 回調(diào)函數(shù)。

在這個 increment 函數(shù)中夺欲,我們的示例調(diào)用了兩次 setState跪帝,
用于說明這個兩個 setState 的處理邏輯其實(shí)是不同的

每個 setState 都會重新創(chuàng)建一個 update 對象些阅,
update 構(gòu)成了一個循環(huán)鏈表伞剑,用于記錄所有待更新的操作,結(jié)構(gòu)如下市埋,

update: {
  expirationTime, suspenseConfig, action, 
  eagerReducer, eagerState, 
  next, priority,
}

雖然 update 對象都會創(chuàng)建黎泣,但卻只有第一個 setStateupdate 會被執(zhí)行。
剩下 setStateupdate 會記錄在鏈表中腰素。

(2)更新狀態(tài)

update 鏈表中的后續(xù)操作聘裁,會在下次調(diào)用 useState 的時候執(zhí)行。

仔細(xì)來看的話弓千,React 響應(yīng)完事件后衡便,會進(jìn)入 workLoopSync 循環(huán),
然后調(diào)用了 updateFunctionComponent洋访,更新組件狀態(tài)镣陕。
此時會再次調(diào)用 App 函數(shù),useState 也被再次調(diào)用姻政。

然后 useState 執(zhí)行了和首次渲染不同的邏輯呆抑,調(diào)用了 updateState
執(zhí)行 update 循環(huán)鏈表中的所有的操作汁展,
最后把更新的結(jié)果返回鹊碍。

return [hook.memoizedState, dispatch];

此時 hook.memoizedState 是所有 setState 都執(zhí)行后的結(jié)果了。

Reconcile & Commit

剩下的流程就跟首次渲染大同小異了食绿,
先是調(diào)用 reconcileChildren 更新 fiber 樹侈咕,然后 commitRoot 渲染到頁面上。

總結(jié)

本文簡單梳理了 react@16.13.1 useStatesetState 的業(yè)務(wù)邏輯器紧,
可以看到 hook 的主要神秘之處在于耀销,update 暫存起來異步更新囤攀。

以上分析還只是 React 源碼的九牛之一毛澄干,還需要不斷的學(xué)習(xí)探索。

參考

github: debug-react
react@16.13.1

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末锻狗,一起剝皮案震驚了整個濱河市掌腰,隨后出現(xiàn)的幾起案子狰住,更是在濱河造成了極大的恐慌,老刑警劉巖齿梁,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件催植,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)查邢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酵幕,“玉大人扰藕,你說我怎么就攤上這事》既觯” “怎么了邓深?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長笔刹。 經(jīng)常有香客問我芥备,道長,這世上最難降的妖魔是什么舌菜? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任萌壳,我火速辦了婚禮,結(jié)果婚禮上日月,老公的妹妹穿的比我還像新娘袱瓮。我一直安慰自己,他們只是感情好爱咬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布尺借。 她就那樣靜靜地躺著,像睡著了一般精拟。 火紅的嫁衣襯著肌膚如雪燎斩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天蜂绎,我揣著相機(jī)與錄音栅表,去河邊找鬼。 笑死荡碾,一個胖子當(dāng)著我的面吹牛谨读,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坛吁,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼劳殖,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了拨脉?” 一聲冷哼從身側(cè)響起哆姻,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎玫膀,沒想到半個月后矛缨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年箕昭,在試婚紗的時候發(fā)現(xiàn)自己被綠了灵妨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡落竹,死狀恐怖泌霍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情述召,我是刑警寧澤朱转,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站积暖,受9級特大地震影響藤为,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜夺刑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一缅疟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧遍愿,春花似錦窿吩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至倾哺,卻和暖如春轧邪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背羞海。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工忌愚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人却邓。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓硕糊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親腊徙。 傳聞我的和親對象是個殘疾皇子简十,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評論 2 355