<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)

          共 6229字,需瀏覽 13分鐘

           ·

          2020-11-12 23:29

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

          React Hooks 可以說完全顛覆了之前 Class Component 的寫法,進一步增強了狀態(tài)復(fù)用的能力,讓 Function Component 也具有了內(nèi)部狀態(tài),對于我個人來說,更加喜歡 Hooks 的寫法。當然如果你是一個使用 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)下的?updateQueue、effectList?都是鏈表的數(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,而?dispatch?由?dispatchAction?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?如出一轍。之前我們通過?updateNum?對?num?連續(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()?{
          ??//?取出當前?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、updateQueue、Hooks)。

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



          ●?前端入門機器學(xué)習(xí) Tensorflow.js 簡明教程

          ●?簡單代碼的秘訣

          ●?【讀懂源碼】React 架構(gòu)的演變 - 從同步到異步



          ·END·

          圖雀社區(qū)

          匯聚精彩的免費實戰(zhàn)教程



          關(guān)注公眾號回復(fù) z 拉學(xué)習(xí)交流群


          喜歡本文,點個“在看”告訴我

          瀏覽 31
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  A片在线视频 | 男女男精品网站 | 熟女三级片 | 亚洲精品A片 | 天天爽天天爽成人A片影院 |