<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          React 架構(gòu)的演變 - Hooks 的實現(xiàn)

          共 6113字,需瀏覽 13分鐘

           ·

          2020-11-07 03:08

          這是這個系列的最后一篇文章了,終于收尾了? 。

          React Hooks 可以說完全顛覆了之前 Class Component 的寫法,進一步增強了狀態(tài)復(fù)用的能力,讓 Function Component 也具有了內(nèi)部狀態(tài),對于我個人來說,更加喜歡 Hooks 的寫法。當(dāng)然如果你是一個使用 Class Component ?的老手,初期上手時會覺得很苦惱,畢竟之前沉淀的很多 HOC、Render Props 組件基本沒法用。而且之前的 Function Component 是無副作用的無狀態(tài)組件,現(xiàn)在又能通過 Hooks 引入狀態(tài),看起來真的很讓人疑惑。Function Component 的另一個優(yōu)勢就是可以完全告別 this ,在 Class Component 里面 this 真的是一個讓人討厭的東西? 。

          Hook 如何與組件關(guān)聯(lián)

          在之前的文章中多次提到,F(xiàn)iber 架構(gòu)下的 updateQueueeffectList 都是鏈表的數(shù)據(jù)結(jié)構(gòu),然后掛載的 Fiber 節(jié)點上。而一個函數(shù)組件內(nèi)所有的 Hooks 也是通過鏈表的形式存儲的,最后掛載到 ?fiber.memoizedState 上。

          function?App()?{
          ??const?[num,?updateNum]?=?useState(0)

          ??return?<div
          ????onClick={()?=>
          ?updateNum(num?=>?num?+?1)}
          ??>{?num?}div>

          }

          export?default?App

          我們先簡單看下,調(diào)用 useState 時,構(gòu)造鏈表的過程:

          var?workInProgressHook?=?null
          var?HooksDispatcherOnMount?=?{
          ??useState:?function?(initialState)?{
          ????return?mountState(initialState)
          ??}
          }

          function?function?mountState(initialState)?{
          ??//?新的?Hook?節(jié)點
          ??var?hook?=?mountWorkInProgressHook()
          ??//?緩存初始值
          ??hook.memoizedState?=?initialState
          ??//?構(gòu)造更新隊列,類似于?fiber.updateQueue
          ??var?queue?=?hook.queue?=?{
          ????pending:?null,
          ????dispatch:?null,
          ????lastRenderedState:?initialState
          ??}
          ??//?用于派發(fā)更新
          ??var?dispatch?=?queue.dispatch?=?dispatchAction.bind(
          ????null,?workInProgress,?queue
          ??)
          ??//?[num,?updateNum]?=?useState(0)
          ??return?[hook.memoizedState,?dispatch]
          }

          function?mountWorkInProgressHook()?{
          ??var?hook?=?{
          ????memoizedState:?null,
          ????baseState:?null,
          ????baseQueue:?null,
          ????queue:?null,
          ????next:?null
          ??}

          ??if?(workInProgressHook?===?null)?{
          ????//?構(gòu)造鏈表頭節(jié)點
          ????workInProgress.memoizedState?=?workInProgressHook?=?hook
          ??}?else?{
          ????//?如果鏈表已經(jīng)存在,在掛載到?next
          ????workInProgressHook?=?workInProgressHook.next?=?hook
          ??}

          ??return?workInProgressHook
          }
          Hook

          如果此時有兩個 Hook,第二個 Hook 就會掛載到第一個 Hook 的 next 屬性上。

          function?App()?{
          ??const?[num,?updateNum]?=?useState(0)
          ??const?[str,?updateStr]?=?useState('value:?')

          ??return?<div
          ????onClick={()?=>
          ?updateNum(num?=>?num?+?1)}
          ??>{?str?}?{?num?}div>

          }

          export?default?App
          Hook

          Hook 的更新隊列

          Hook 通過 .next 彼此相連,而每個 Hook 對象下,還有個 queue 字段,該字段和 Fiber 節(jié)點上的 updateQueue 一樣,是一個更新隊列在,上篇文章 《React 架構(gòu)的演變-更新機制》中有講到,React Fiber 架構(gòu)中,更新隊列通過鏈表結(jié)構(gòu)進行存儲。

          class?App?extends?React.Component?{
          ??state?=?{?val:?0?}
          ??click?()?{
          ????for?(let?i?=?0;?i?3;?i++)?{
          ??????this.setState({?val:?this.state.val?+?1?})
          ????}
          ??}
          ??render()?{
          ????return?<div?onClick={()?=>?{
          ??????this.click()
          ????}}>val:?{?this.state.val?}div>

          ??}
          }

          點擊 div 之后,產(chǎn)生的 3 次 setState 通過鏈表的形式掛載到 fiber.updateQueue 上,待到 MessageChannel 收到通知后,真正執(zhí)行更新操作時,取出更新隊列,將計算結(jié)果更新到 fiber.memoizedState。

          setState

          hook.queue 的邏輯和 fiber.updateQueue 的邏輯也是完全一致的。

          function?App()?{
          ??const?[num,?updateNum]?=?useState(0)

          ??return?<div
          ????onClick={()?=>
          ?{
          ??????//?連續(xù)更新?3?次
          ??????updateNum(num?=>?num?+?1)
          ??????updateNum(num?=>?num?+?1)
          ??????updateNum(num?=>?num?+?1)
          ????}}
          ??>
          ????{?num?}
          ??div>

          }

          export?default?App;
          var?dispatch?=?queue.dispatch?=?dispatchAction.bind(
          ??null,?workInProgress,?queue
          )
          //?[num,?updateNum]?=?useState(0)
          return?[hook.memoizedState,?dispatch]

          調(diào)用 useState 的時候,返回的數(shù)組第二個參數(shù)為 dispatch,而 dispatchdispatchAction bind 后得到。

          function?dispatchAction(fiber,?queue,?action)?{
          ??var?update?=?{
          ????next:?null,
          ????action:?action,
          ????//?省略調(diào)度相關(guān)的參數(shù)...
          ??};

          ??var?pending?=?queue.pending
          ??if?(pending?===?null)?{
          ????update.next?=?update
          ??}?else?{
          ????update.next?=?pending.next
          ????pending.next?=?update
          ??}
          ??queue.pending?=?update

          ??//?執(zhí)行更新
          ??scheduleUpdateOnFiber()
          }

          可以看到這里構(gòu)造鏈表的方式與 fiber.updateQueue 如出一轍。之前我們通過 updateNumnum 連續(xù)更新了 3 次,最后形成的更新隊列如下:

          更新隊列

          函數(shù)組件的更新

          前面的文章分享過,F(xiàn)iber 架構(gòu)下的更新流程分為遞(beginWork)、歸(completeWork)兩個步驟,在 beginWork 中,會依據(jù)組件類型進行 render 操作構(gòu)造子組件。

          function?beginWork(current,?workInProgress)?{
          ??switch?(workInProgress.tag)?{
          ????//?其他類型組件代碼省略...
          ????case?FunctionComponent:?{
          ??????//?這里的?type?就是函數(shù)組件的函數(shù)
          ??????//?例如,前面的?App?組件,type?就是?function?App()?{}
          ??????var?Component?=?workInProgress.type
          ??????var?resolvedProps?=?workInProgress.pendingProps
          ??????//?組件更新
          ??????return?updateFunctionComponent(
          ????????current,?workInProgress,?Component,?resolvedProps
          ??????)
          ????}
          ??}
          }

          function?updateFunctionComponent(
          ?current,?workInProgress,?Component,?nextProps
          )?
          {
          ??//?構(gòu)造子組件
          ??var?nextChildren?=?renderWithHooks(
          ????current,?workInProgress,?Component,?nextProps
          ??)
          ??reconcileChildren(current,?workInProgress,?nextChildren)
          ??return?workInProgress.child
          }

          看名字就能看出來,renderWithHooks 方法就是構(gòu)造帶 Hooks 的子組件。

          function?renderWithHooks(
          ?current,?workInProgress,?Component,?props
          )?
          {
          ??if?(current?!==?null?&&?current.memoizedState?!==?null)?{
          ????ReactCurrentDispatcher.current?=?HooksDispatcherOnUpdate
          ??}?else?{
          ????ReactCurrentDispatcher.current?=?HooksDispatcherOnMount
          ??}
          ??var?children?=?Component(props)
          ??return?children
          }

          從上面的代碼可以看出,函數(shù)組件更新或者首次渲染時,本質(zhì)就是將函數(shù)取出執(zhí)行了一遍。不同的地方在于給 ReactCurrentDispatcher 進行了不同的賦值,而 ReactCurrentDispatcher 的值最終會影響 useState 調(diào)用不同的方法。

          根據(jù)之前文章講過的雙緩存機制,current 存在的時候表示是更新操作,不存在的時候表示首次渲染。

          function?useState(initialState)?{
          ??//?首次渲染時指向?HooksDispatcherOnMount
          ??//?更新操作時指向?HooksDispatcherOnUpdate
          ??var?dispatcher?=?ReactCurrentDispatcher.current
          ??return?dispatcher.useState(initialState)
          }

          HooksDispatcherOnMount.useState 的代碼前面已經(jīng)介紹過,這里不再著重介紹。

          //?HooksDispatcherOnMount?的代碼前面已經(jīng)介紹過
          var?HooksDispatcherOnMount?=?{
          ??useState:?function?(initialState)?{
          ????return?mountState(initialState)
          ??}
          }

          我們重點看看 HooksDispatcherOnMount.useState 的邏輯。

          var?HooksDispatcherOnUpdateInDEV?=?{
          ??useState:?function?(initialState)?{
          ????return?updateState()
          ??}
          }

          function?updateState()?{
          ??//?取出當(dāng)前?hook
          ??workInProgressHook?=?nextWorkInProgressHook
          ??nextWorkInProgressHook?=?workInProgressHook.next

          ??var?hook?=?nextWorkInProgressHook
          ??var?queue?=?hook.queue
          ??var?pendingQueue?=?queue.pending

          ??//?處理更新
          ??var?first?=?pendingQueue.next
          ??var?state?=?hook.memoizedState
          ??var?update?=?first

          ??do?{
          ????var?action?=?update.action
          ????state?=?typeof?action?===?'function'???action(state)?:?action

          ????update?=?update.next;
          ??}?while?(update?!==?null?&&?update?!==?first)


          ??hook.memoizedState?=?state

          ??var?dispatch?=?queue.dispatch
          ??return?[hook.memoizedState,?dispatch]
          }

          如果有看之前的 setState 的代碼,這里的邏輯其實是一樣的。將更新對象的 action 取出,如果是函數(shù)就執(zhí)行,如果不是函數(shù)就直接對 state 進行替換操作。

          總結(jié)

          React 系列的文章終于寫完了,這一篇文章應(yīng)該是最簡單的一篇,如果想拋開 React 源碼,單獨看 Hooks 實現(xiàn)可以看這篇文章:《React Hooks 原理》。Fiber 架構(gòu)為了能夠?qū)崿F(xiàn)循環(huán)的方式更新,將所有涉及到數(shù)據(jù)的地方結(jié)構(gòu)都改成了鏈表,這樣的優(yōu)勢就是可以隨時中斷,為異步模式讓路,F(xiàn)iber 樹就像一顆圣誕樹,上面掛滿了各種彩燈(alternate、EffectList、updateQueueHooks)。

          推薦大家可以將這個系列從頭到尾看一遍,相信會特別有收獲的。


          瀏覽 57
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产AV影院 | 色婷婷五月天亚洲中文字幕 | 青青草视频成人 | 欧美一级片在线看 | 操逼一区二区 |