1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        【讀懂源碼】React 架構(gòu)的演變 - Hooks 的實(shí)現(xiàn)

        共 6229字,需瀏覽 13分鐘

         ·

        2020-11-12 23:29

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

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

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

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

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

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

        }

        export?default?App

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

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

        function?function?mountState(initialState)?{
        ??//?新的?Hook?節(jié)點(diǎn)
        ??var?hook?=?mountWorkInProgressHook()
        ??//?緩存初始值
        ??hook.memoizedState?=?initialState
        ??//?構(gòu)造更新隊(duì)列,類(lèi)似于?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é)點(diǎn)
        ????workInProgress.memoizedState?=?workInProgressHook?=?hook
        ??}?else?{
        ????//?如果鏈表已經(jīng)存在,在掛載到?next
        ????workInProgressHook?=?workInProgressHook.next?=?hook
        ??}

        ??return?workInProgressHook
        }
        Hook

        如果此時(shí)有兩個(gè) Hook,第二個(gè) Hook 就會(huì)掛載到第一個(gè) 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 的更新隊(duì)列

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

        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>

        ??}
        }

        點(diǎn)擊 div 之后,產(chǎn)生的 3 次 setState 通過(guò)鏈表的形式掛載到?fiber.updateQueue?上,待到 MessageChannel 收到通知后,真正執(zhí)行更新操作時(shí),取出更新隊(duì)列,將計(jì)算結(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ù)組第二個(gè)參數(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?如出一轍。之前我們通過(guò)?updateNum?對(duì)?num?連續(xù)更新了 3 次,最后形成的更新隊(duì)列如下:

        更新隊(duì)列

        函數(shù)組件的更新

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

        function?beginWork(current,?workInProgress)?{
        ??switch?(workInProgress.tag)?{
        ????//?其他類(lèi)型組件代碼省略...
        ????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
        }

        看名字就能看出來(lái),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ù)組件更新或者首次渲染時(shí),本質(zhì)就是將函數(shù)取出執(zhí)行了一遍。不同的地方在于給?ReactCurrentDispatcher?進(jìn)行了不同的賦值,而?ReactCurrentDispatcher?的值最終會(huì)影響?useState?調(diào)用不同的方法。

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

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

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

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

        我們重點(diǎn)看看?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 的代碼,這里的邏輯其實(shí)是一樣的。將更新對(duì)象的 action 取出,如果是函數(shù)就執(zhí)行,如果不是函數(shù)就直接對(duì) state 進(jìn)行替換操作。

        總結(jié)

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

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



        ●?前端入門(mén)機(jī)器學(xué)習(xí) Tensorflow.js 簡(jiǎn)明教程

        ●?簡(jiǎn)單代碼的秘訣

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



        ·END·

        圖雀社區(qū)

        匯聚精彩的免費(fèi)實(shí)戰(zhàn)教程



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


        喜歡本文,點(diǎn)個(gè)“在看”告訴我

        瀏覽 31
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            粉嫩被两根大粗黑进出 | 扯下内裤粗大挤进去好涨 | 亚洲天堂最新 | 足交影院| 帮老师解开蕾丝奶罩吸乳漫画 | 在线免费观看黄色视频网站 | 日韩精品福利视频 | 免费看美女操逼的网站 | 影音先锋av在线资源 | 李小冉三级未删减电影 |