<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】709- React Hook 的底層實現(xiàn)原理

          共 7742字,需瀏覽 16分鐘

           ·

          2020-09-08 09:38








          原文鏈接:

          https://medium.com/the-guild/under-the-hood-of-reacts-hooks-system-eb59638c9dba






          前言


          ? ? ? ?本文將會深入React hooks的實現(xiàn)來讓我們更加了解它。這個神奇的特性存在的問題是,一旦出現(xiàn)問題就很難調(diào)試,因為它有復(fù)雜的堆棧跟蹤支持。因此,通過深入理解React hooks的系統(tǒng),我們就可以在遇到問題時非常快的解決它們,甚至可以提前避免錯誤發(fā)生。

          ? ? ? ? 在我開始之前,我首先要聲明我并不是React的開發(fā)者/維護者,因此,大家不要太信任我的觀點。我確實非常深入地研究了React hooks的實現(xiàn),但是無論如何我也不能保證這就是hooks的實際實現(xiàn)原理。話雖如此,我已經(jīng)用React源碼來支持我的觀點,并嘗試著使我的論點盡可能的真實。



          ? ? ? ? 首先,讓我們進入需要確保hooks在React的作用域調(diào)用的機制,因為你現(xiàn)在可能知道如果在沒有正確的上下文調(diào)用鉤子是沒有意義的:


          The dispatcher

          ? ? dispatcher 是包含了hooks函數(shù)的共享對象。它將根據(jù)ReactDom的渲染階段來動態(tài)分配或者清除,并且確保用戶無法在 React 組件外訪問hooks。

          ? ? ?我們可以在渲染根組件前通過簡單的切換來使用正確的dispatcher,用一個叫做enableHooks的標志來開啟/禁用;這意味著從技術(shù)上來說,我們可以在運行時開啟/禁用掛鉤。React 16.6.x就已經(jīng)有了試驗性的實現(xiàn),只不過它是被禁用的。

          ? ? 當我們執(zhí)行完渲染工作時,我們將dispatcher 置空從而防止它在ReactDOM的渲染周期之外被意外調(diào)用。這是一種可以確保用戶不做傻事的機制。

          ? ? dispatcher 在每一個 hook 調(diào)用中 使用resolveDispatcher()這個函數(shù)來調(diào)用。就像我之前說的,在React的渲染周期之外調(diào)用是毫無意義的,并且React會打印出警告信息“Hooks只能在函數(shù)組件的主體內(nèi)部調(diào)用”


          let?currentDispatcher
          const?dispatcherWithoutHooks?=?{?/*?...?*/?}
          const?dispatcherWithHooks?=?{?/*?...?*/?}

          function?resolveDispatcher()?{
          ??if?(currentDispatcher)?return?currentDispatcher
          ??throw?Error("Hooks?can't?be?called")
          }

          function?useXXX(...args)?{
          ??const?dispatcher?=?resolveDispatcher()
          ??return?dispatcher.useXXX(...args)
          }

          function?renderRoot()?{
          ??currentDispatcher?=?enableHooks???dispatcherWithHooks?:?dispatcherWithoutHooks
          ??performWork()
          ??currentDispatcher?=?null
          }


          ? ? ? ?到此為止既然我們已經(jīng)看過了這種簡單的封裝機制,我希望我們轉(zhuǎn)到本文的核心 - Hooks。我想向您介紹一個新概念:


          The hooks queue



          ? ? ? ? 在使用場景之后,hooks表示為在調(diào)用順序下鏈接在一起的節(jié)點。它們被表示成這樣是因為hooks并不是簡單的創(chuàng)建然后又把它遺留下來。它們有一種可以讓他們變成它們自己的機制。一個Hook有幾個我希望你可以在深入研究實現(xiàn)之前記住的屬性:

          1. 它的初始狀態(tài)在首次渲染時被創(chuàng)建。
          2. 她的狀態(tài)可以即時更新。
          3. React會在之后的渲染中記住hook的狀態(tài)
          4. React會根據(jù)調(diào)用順序為您提供正確的狀態(tài)
          5. React會知道這個hook屬于哪個Fiber。

          因此,我們需要重新思考我們查看組件狀態(tài)的方式。到目前為止,我們認為它就像是一個普通的對象:
          {
          ??foo:?'foo',
          ??bar:?'bar',
          ??baz:?'baz',
          }
          ? ??
          但是在處理hook時,它應(yīng)該被視為一個隊列,其中每個節(jié)點代表一個狀態(tài)的單個模型:
          {
          ??memoizedState:?'foo',
          ??next:?{
          ????memoizedState:?'bar',
          ????next:?{
          ??????memoizedState:?'bar',
          ??????next:?null
          ????}
          ??}
          }

          ? ? ?可以在實現(xiàn)中查看單個hook節(jié)點的模式。你會看到hook有一些額外的屬性,但是理解鉤子如何工作的關(guān)鍵在于memoizedState和next。其余屬性由useReducer()hook專門用于緩存已經(jīng)調(diào)度的操作和基本狀態(tài),因此在各種情況下,還原過程可以作為后備重復(fù):

          · baseState - 將給予reducer的狀態(tài)對象。
          · baseUpdate- 最近的創(chuàng)建了最新baseState的調(diào)度操作。
          · queue - 調(diào)度操作的隊列,等待進入reducer。

          ? ? ? 不幸的是,我沒有設(shè)法很好地掌握reducer hook,因為我沒有設(shè)法重現(xiàn)任何邊緣情況,所以我不覺得舒服去精心設(shè)計。我只能說,reducer 的實現(xiàn)是如此不一致,在代碼注釋中甚至指出,“不知道這些是否都是所需的語義”; 所以我該如何確定?!

          ? ? ? 所以回到hooks,在每個函數(shù)組件調(diào)用之前,將調(diào)用一個名為prepareHooks()的函數(shù),其中當前fiber及其hooks隊列中的第一個hook節(jié)點將被存儲在全局變量中。這樣,只要我們調(diào)用一個hook函數(shù)(useXXX()),就會知道要在哪個上下文中運行。
          let?currentlyRenderingFiber
          let?workInProgressQueue
          let?currentHook

          //?Source:?https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:123
          function?prepareHooks(recentFiber)?{
          ??currentlyRenderingFiber?=?workInProgressFiber
          ??currentHook?=?recentFiber.memoizedState
          }

          //?Source:?https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:148
          function?finishHooks()?{
          ??currentlyRenderingFiber.memoizedState?=?workInProgressHook
          ??currentlyRenderingFiber?=?null
          ??workInProgressHook?=?null
          ??currentHook?=?null
          }

          //?Source:?https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:115
          function?resolveCurrentlyRenderingFiber()?{
          ??if?(currentlyRenderingFiber)?return?currentlyRenderingFiber
          ??throw?Error("Hooks?can't?be?called")
          }
          //?Source:?https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:267
          function?createWorkInProgressHook()?{
          ??workInProgressHook?=?currentHook???cloneHook(currentHook)?:?createNewHook()
          ??currentHook?=?currentHook.next
          ??workInProgressHook
          }

          function?useXXX()?{
          ??const?fiber?=?resolveCurrentlyRenderingFiber()
          ??const?hook?=?createWorkInProgressHook()
          ??//?...
          }

          function?updateFunctionComponent(recentFiber,?workInProgressFiber,?Component,?props)?{
          ??prepareHooks(recentFiber,?workInProgressFiber)
          ??Component(props)
          ??finishHooks()
          }

          ? ? ? ? ?一旦更新完成,一個叫做finishHooks()的函數(shù)將被調(diào)用,其中hooks隊列中第一個節(jié)點的引用將存儲在渲染完成的fiber對象的memoizedState屬性中。這意味著hooks隊列及其狀態(tài)可以在外部被定位到:


          const?ChildComponent?=?()?=>?{
          ??useState('foo')
          ??useState('bar')
          ??useState('baz')

          ??return?null
          }

          const?ParentComponent?=?()?=>?{
          ??const?childFiberRef?=?useRef()

          ??useEffect(()?=>?{
          ????let?hookNode?=?childFiberRef.current.memoizedState

          ????assert(hookNode.memoizedState,?'foo')
          ????hookNode?=?hooksNode.next
          ????assert(hookNode.memoizedState,?'bar')
          ????hookNode?=?hooksNode.next
          ????assert(hookNode.memoizedState,?'baz')
          ??})

          ??return?(
          ????<ChildComponent?ref={childFiberRef}?/>
          ??)
          }


          ? ? 讓我們更具體一點,談?wù)劯鱾€hooks,從最常見的state hook開始:


          State hooks


          ? ? ?你將驚訝的了解到useState hook使用的useReducer只是為它提供了一個預(yù)定義的reducer處理程序。這意味著實際上useState返回的結(jié)果是一個reducer狀態(tài)和一個action dispatcher。我希望你看一下state hook使用的reducer處理程序:

          function?basicStateReducer(state,?action)?{
          ??return?typeof?action?===?'function'???action(state)?:?action;
          }

          ? ? 正如預(yù)期的那樣,我們可以直接為action dispatcher提供新的狀態(tài); 但你會看那個嗎?!我們還可以為dispatcher提供一個動作函數(shù),該函數(shù)將接收舊狀態(tài)并返回新狀態(tài)。這意味著,當你將狀態(tài)設(shè)置器傳遞到子組件時,你可以改變當前父組件的狀態(tài),不需要作為一個不同的prop傳遞下去。
          ? ? ? 舉個例子:
          const?ParentComponent?=?()?=>?{
          ??const?[name,?setName]?=?useState()
          ??
          ??return?(
          ????<ChildComponent?toUpperCase={setName}?/>
          ??)
          }

          const?ChildComponent?=?(props)?=>?{
          ??useEffect(()?=>?{
          ????props.toUpperCase((state)?=>?state.toUpperCase())
          ??},?[true])
          ??
          ??return?null
          }


          ? ? 最后,effect hooks? - 它對組件的生命周期及其工作方式產(chǎn)生了重大影響:


          Effect hooks


          ? ? Effect hooks 的行為略有不同,并且有一個額外的邏輯層,我接下來會解釋。同樣,在我深入了解實現(xiàn)之前,我希望你能記住effect hooks的屬性:


          1. 它們是在渲染時創(chuàng)建的,但它們在繪制后運行。

          2. 它們將在下一次繪制之前被銷毀。

          3. 它們按照已經(jīng)被定義的順序執(zhí)行。



          ? ? ?請注意,我使用的是“繪制”術(shù)語,而不是“渲染”。這兩個是不同的東西,我看到最近React Conf中的許多發(fā)言者使用了錯誤的術(shù)語!即使在官方的React文檔中,他們也會說“在渲染屏幕之后”,在某種意義上應(yīng)該更像“繪制”。render方法只創(chuàng)建fiber節(jié)點,但沒有繪制任何東西。


          ? ? ? ? 因此,應(yīng)該有另一個額外的隊列保持這些effect,并應(yīng)在繪制后處理。一般而言,fiber保持包含effect節(jié)點的隊列。

          每種effect都是不同的類型,應(yīng)在適當?shù)碾A段處理


          在變化之前調(diào)用實例的getSnapshotBeforeUpdate()方法。

          執(zhí)行所有節(jié)點的插入,更新,刪除和ref卸載操作。

          執(zhí)行所有生命周期和ref回調(diào)。生命周期作為單獨的過程發(fā)生,因此整個樹中的所有放置,更新和刪除都已經(jīng)被調(diào)用。此過程還會觸發(fā)任何特定渲染的初始effects。


          由useEffect() hook 安排的effects - 基于實現(xiàn)也被稱為“passive effects” (也許我們應(yīng)該在React社區(qū)中開始使用這個術(shù)語?!)。



          當涉及到hook effects時,它們應(yīng)該存儲在fiber的一個名為 updateQueue的屬性中。
          每個effect node應(yīng)該具有以下模式


          tag - 一個二進制數(shù),它將決定effect的行為

          create- 繪制后應(yīng)該運行的回調(diào)

          destroy- 從create()返回的回調(diào)應(yīng)該在初始渲染之前運行。


          inputs - 一組值,用于確定是否應(yīng)銷毀和重新創(chuàng)建effe


          ?next - 函數(shù)組件中定義的下一個effect的引用。


          ? ? ? 除了tag屬性外,其他屬性都非常簡單易懂。如果你已經(jīng)很好地研究了hooks,你就會知道React為你提供了幾個特殊的hooks:useMutationEffect()和useLayoutEffect()。這兩種效果在內(nèi)部使用useEffect(),這實際上意味著它們創(chuàng)建了一個effect節(jié)點,但它們使用不同的tag值。

          標簽由二進制值組合而成:


          const?NoEffect?=?/*?????????????*/?0b00000000;
          const?UnmountSnapshot?=?/*??????*/?0b00000010;
          const?UnmountMutation?=?/*??????*/?0b00000100;
          const?MountMutation?=?/*????????*/?0b00001000;
          const?UnmountLayout?=?/*????????*/?0b00010000;
          const?MountLayout?=?/*??????????*/?0b00100000;
          const?MountPassive?=?/*?????????*/?0b01000000;
          const?UnmountPassive?=?/*???????*/?0b10000000;

          這些二進制值的最常見用例是使用管道(|)將這些位按原樣添加到單個值。然后我們可以使用&符號(&)檢查標簽是否實現(xiàn)某種行為。如果結(jié)果為非零,則表示tag實現(xiàn)了指定的行為。

          以下是React支持的hook effect類型及其標簽:
          Default?effect?—?UnmountPassive?|?MountPassive.
          Mutation?effect?—?UnmountSnapshot?|?MountMutation.
          Layout?effect?—?UnmountMutation?|?MountLayout.
          以下是React如何檢查行為實現(xiàn):
          if?((effect.tag?&?unmountTag)?!==?NoHookEffect)?{
          ??//?Unmount
          }
          if?((effect.tag?&?mountTag)?!==?NoHookEffect)?{
          ??//?Mount
          }


          因此,基于我們剛剛學到的關(guān)于effect hooks的內(nèi)容,我們實際上可以在外部向某個fiber注入effect:


          function?injectEffect(fiber)?{
          ??const?lastEffect?=?fiber.updateQueue.lastEffect

          ??const?destroyEffect?=?()?=>?{
          ????console.log('on?destroy')
          ??}

          ??const?createEffect?=?()?=>?{
          ????console.log('on?create')

          ????return?destroy
          ??}

          ??const?injectedEffect?=?{
          ????tag:?0b11000000,
          ????next:?lastEffect.next,
          ????create:?createEffect,
          ????destroy:?destroyEffect,
          ????inputs:?[createEffect],
          ??}

          ??lastEffect.next?=?injectedEffect
          }

          const?ParentComponent?=?(
          ??<ChildComponent?ref={injectEffect}?/>
          )






          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設(shè)計模式 重溫系列(9篇全)
          4.?正則 / 框架 / 算法等 重溫系列(16篇全)
          5.?Webpack4 入門(上)||?Webpack4 入門(下)
          6.?MobX 入門(上)?||??MobX 入門(下)
          7.?70+篇原創(chuàng)系列匯總

          回復(fù)“加群”與大佬們一起交流學習~

          點擊“閱讀原文”查看70+篇原創(chuàng)文章

          瀏覽 47
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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片视频 | 青青草操逼 | 色香蕉网 | 韩国国产精品 |