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

          useEffect和useLayoutEffect的原理與區(qū)別

          共 9676字,需瀏覽 20分鐘

           ·

          2022-04-30 12:30

          本文適合對React的use(Layout)Effect有疑惑的小伙伴閱讀。

          歡迎關(guān)注前端早茶,與廣東靚仔共同進階~

          一、前言

          在React項目中,我們使用hook開發(fā)項目,有時候會由于對hook的執(zhí)行機制、原理不是很清楚,遇到一些"難以解決的"問題。
          其實當(dāng)我們遇到問題的時候,我們應(yīng)該將問題收集起來,解決完問題后進行一個梳理、總結(jié),最好能舉一反三。以后就不會再遇到這類型的問題了。
          下面我們會從effect的數(shù)據(jù)結(jié)構(gòu)入手,梳理use(Layout)Effect在rendercommit階段的整體流程。

          二、正文

          Effect的數(shù)據(jù)結(jié)構(gòu)

          對函數(shù)組件來說,其fiber上的memorizedState專門用來存儲hooks鏈表,每一個hook對應(yīng)鏈表中的每一個元素。

          useEffect/useLayoutEffect產(chǎn)生的hook會放到fiber.memorizedState上,而它們調(diào)用后最終會生成一個effect對象,存儲到它們對應(yīng)hook的memoizedState中,與其他的effect連接成環(huán)形鏈表。

          單個的effect對象包括以下幾個屬性:

          • create: 傳入use(Layout)Effect函數(shù)的第一個參數(shù),即回調(diào)函數(shù)
          • destroy: 回調(diào)函數(shù)return的函數(shù),在該effect銷毀的時候執(zhí)行
          • deps: 依賴項
          • next: 指向下一個effect
          • tag: effect的類型,區(qū)分是useEffect還是useLayoutEffect

          單純看effect對象中的字段,很容易和平時的用法聯(lián)系起來。create函數(shù)即我們傳入useEffect/useLayoutEffect的回調(diào)函數(shù),而通過deps,可以控制create是否執(zhí)行,如需清除effect,則在create函數(shù)中return一個新函數(shù)(即destroy)即可。

          為了理解effect的數(shù)據(jù)結(jié)構(gòu),假設(shè)有如下組件:

          const?UseEffectExp?=?()?=>?{
          ????const?[?text,?setText?]?=?useState('hello')
          ????useEffect(()?=>?{
          ????????console.log('effect1')
          ????????return?()?=>?{
          ????????????console.log('destory1');
          ????????}
          ????})
          ????useLayoutEffect(()?=>?{
          ????????console.log('effect2')
          ????????return?()?=>?{
          ????????????console.log('destory2');
          ????????}
          ????})
          ????return?<div>effectdiv>
          }


          掛載到它fiber上memoizedState的hooks鏈表結(jié)構(gòu)如下



          例如useEffect hook上的memoizedState存儲了useEffect 的 effect對象(effect1),next指向useLayoutEffect的effect對象(effect2)。effect2的next又指回effect1.在下面的useLayoutEffect hook中,也是如此的結(jié)構(gòu)。



          effect除了保存在fiber.memoizedState對應(yīng)的hook中,還會保存在fiber的updateQueue中。


          現(xiàn)在,我們知道,調(diào)用use(Layout)Effect,最后會產(chǎn)生effect鏈表,這個鏈表保存在兩個地方:

          • fiber.memoizedState的hooks鏈表中,use(Layout)Effect對應(yīng)hook元素的memoizedState中。

          • fiber.updateQueue中,本次更新的updateQueue,它會在本次更新的commit階段中被處理。

          流程概述

          基于上面的數(shù)據(jù)結(jié)構(gòu),對于use(Layout)Effect來說,React做的事情就是
          • render階段:函數(shù)組件開始渲染的時候,創(chuàng)建出對應(yīng)的hook鏈表掛載到
            workInProgress的memoizedState上,并創(chuàng)建effect鏈表,
            但是基于上次和本次依賴項的比較結(jié)果, 創(chuàng)建的effect是有差異的。
            這一點暫且可以理解為:依賴項有變化,effect可以被處理,否則不會被處理。
          • commit階段:異步調(diào)度useEffect,layout階段同步處理useLayoutEffect的
            effect。等到commit階段完成,更新應(yīng)用到頁面上之后,開始處理useEffect
            產(chǎn)生的effect。
          第二點提到了一個重點,就是useEffect和useLayoutEffect的執(zhí)行時機不一樣,前者被異步調(diào)度,當(dāng)頁面渲染完成后再去執(zhí)行,不會阻塞頁面渲染。后者是在commit階段新的DOM準(zhǔn)備完成,但還未渲染到屏幕之前,同步執(zhí)行。

          三、實現(xiàn)細節(jié)

          通過整體流程可以看出,effect的整個過程涉及到render階段和commit階段。render階段只創(chuàng)建effect鏈表,commit階段去處理這個鏈表。所有實現(xiàn)的細節(jié)都是在圍繞effect鏈表。

          render階段-創(chuàng)建effect鏈表

          在實際的使用中,我們調(diào)用的use(Layout)Effect函數(shù),在掛載和更新的過程是不同的。

          掛載時,調(diào)用的是mountEffectImpl,它會為use(Layout)Effect這類hook創(chuàng)建一個hook對象,將workInProgressHook指向它,然后在這個fiber節(jié)點的flag中加入副作用相關(guān)的effectTag。最后,會構(gòu)建effect鏈表掛載到fiber的updateQueue,并且也會在hook上的memorizedState掛載effect。

          function?mountEffectImpl(fiberFlags,?hookFlags,?create,?deps):?void?{
          ??//?創(chuàng)建hook對象
          ??const?hook?=?mountWorkInProgressHook();
          ??//?獲取依賴
          ??const?nextDeps?=?deps?===?undefined???null?:?deps;

          ??//?為fiber打上副作用的effectTag
          ??currentlyRenderingFiber.flags?|=?fiberFlags;

          ??//?創(chuàng)建effect鏈表,掛載到hook的memoizedState上和fiber的updateQueue
          ??hook.memoizedState?=?pushEffect(
          ????HookHasEffect?|?hookFlags,
          ????create,
          ????undefined,
          ????nextDeps,
          ??);
          }


          currentlyRenderingFiber 即 workInProgress節(jié)點

          更新時,調(diào)用updateEffectImpl,完成effect鏈表的構(gòu)建。這個過程中會根據(jù)前后依賴項是否變化,從而創(chuàng)建不同的effect對象。具體體現(xiàn)在effect的tag上,如果前后依賴未變,則effect的tag就賦值為傳入的hookFlags,否則,在tag中加入HookHasEffect標(biāo)志位。正是因為這樣,在處理effect鏈表時才可以只處理依賴變化的effect,use(Layout)Effect可以根據(jù)它的依賴變化情況來決定是否執(zhí)行回調(diào)。

          function?updateEffectImpl(fiberFlags,?hookFlags,?create,?deps):?void?{
          ??const?hook?=?updateWorkInProgressHook();
          ??const?nextDeps?=?deps?===?undefined???null?:?deps;
          ??let?destroy?=?undefined;

          ??if?(currentHook?!==?null)?{
          ????//?從currentHook中獲取上一次的effect
          ????const?prevEffect?=?currentHook.memoizedState;
          ????//?獲取上一次effect的destory函數(shù),也就是useEffect回調(diào)中return的函數(shù)
          ????destroy?=?prevEffect.destroy;
          ????if?(nextDeps?!==?null)?{
          ??????const?prevDeps?=?prevEffect.deps;
          ??????//?比較前后依賴,push一個不帶HookHasEffect的effect
          ??????if?(areHookInputsEqual(nextDeps,?prevDeps))?{
          ????????pushEffect(hookFlags,?create,?destroy,?nextDeps);
          ????????return;
          ??????}
          ????}
          ??}

          ??currentlyRenderingFiber.flags?|=?fiberFlags;
          ??//?如果前后依賴有變,在effect的tag中加入HookHasEffect
          ??//?并將新的effect更新到hook.memoizedState上

          ??hook.memoizedState?=?pushEffect(
          ????HookHasEffect?|?hookFlags,
          ????create,
          ????destroy,
          ????nextDeps,
          ??);
          }

          在組件掛載和更新時,有一個區(qū)別,就是掛載期間調(diào)用pushEffect創(chuàng)建effect對象的時候并沒有傳destroy函數(shù),而更新期間傳了,這是因為每次effect執(zhí)行時,都是先執(zhí)行前一次的銷毀函數(shù),再執(zhí)行新effect的創(chuàng)建函數(shù)。而掛載期間,上一次的effect并不存在,執(zhí)行創(chuàng)建函數(shù)前也就無需先銷毀。

          掛載和更新,都調(diào)用了pushEffect,它的職責(zé)很單純,就是創(chuàng)建effect對象,構(gòu)建effect鏈表,掛到WIP節(jié)點的updateQueue上。


          function?pushEffect(tag,?create,?destroy,?deps)?{
          ??//?創(chuàng)建effect對象
          ??const?effect:?Effect?=?{
          ????tag,
          ????create,
          ????destroy,
          ????deps,
          ????//?Circular
          ????next:?(null:?any),
          ??};

          ??//?從workInProgress節(jié)點上獲取到updateQueue,為構(gòu)建鏈表做準(zhǔn)備
          ??let?componentUpdateQueue:?null?|?FunctionComponentUpdateQueue?=?(currentlyRenderingFiber.updateQueue:?any);
          ??if?(componentUpdateQueue?===?null)?{
          ????//?如果updateQueue為空,把effect放到鏈表中,和它自己形成閉環(huán)
          ????componentUpdateQueue?=?createFunctionComponentUpdateQueue();
          ????//?將updateQueue賦值給WIP節(jié)點的updateQueue,實現(xiàn)effect鏈表的掛載
          ????currentlyRenderingFiber.updateQueue?=?(componentUpdateQueue:?any);
          ????componentUpdateQueue.lastEffect?=?effect.next?=?effect;
          ??}?else?{
          ????//?updateQueue不為空,將effect接到鏈表的后邊
          ????const?lastEffect?=?componentUpdateQueue.lastEffect;
          ????if?(lastEffect?===?null)?{
          ??????componentUpdateQueue.lastEffect?=?effect.next?=?effect;
          ????}?else?{
          ??????const?firstEffect?=?lastEffect.next;
          ??????lastEffect.next?=?effect;
          ??????effect.next?=?firstEffect;
          ??????componentUpdateQueue.lastEffect?=?effect;
          ????}
          ??}
          ??return?effect;
          }


          函數(shù)組件和類組件的updateQueue都是環(huán)狀鏈表


          以上,就是effect鏈表的構(gòu)建過程。我們可以看到,effect對象創(chuàng)建出來最終會以兩種形式放到兩個地方:單個的effect,放到hook.memorizedState上;環(huán)狀的effect鏈表,放到fiber節(jié)點的updateQueue中。

          兩者各有用途,前者的effect會作為上次更新的effect,為本次創(chuàng)建effect對象提供參照(對比依賴項數(shù)組),后者的effect鏈表會作為最終被執(zhí)行的主體,帶到commit階段處理。

          commit階段 effect如何被處理

          useEffect和useLayoutEffect,對它們的處理最終都落在處理fiber.updateQueue上,對前者來說,循環(huán)updateQueue時只處理包含useEffect這類tag的effect,對后者來說,只處理包含useLayoutEffect這類tag的effect,它們的處理過程都是先執(zhí)行前一次更新時effect的銷毀函數(shù)(destroy),再執(zhí)行新effect的創(chuàng)建函數(shù)(create)。
          以上是它們的處理過程在微觀上的共性,宏觀上的區(qū)別主要體現(xiàn)在執(zhí)行時機上。useEffect是在beforeMutationlayout階段異步調(diào)度,然后在本次的更新應(yīng)用到屏幕上之后再執(zhí)行,而useLayoutEffect是在layout階段同步執(zhí)行的。下面先分析useEffect的處理過程。

          useEffect的異步調(diào)度

          componentDidMountcomponentDidUpdate 不同的是,在瀏覽器完成布局與繪制之后,傳給 useEffect 的函數(shù)會延遲調(diào)用。這使得它適用于許多常見的副作用場景,比如設(shè)置訂閱和事件處理等情況,因此不應(yīng)在函數(shù)中執(zhí)行阻塞瀏覽器更新屏幕的操作。
          基于useEffect回調(diào)延遲調(diào)用(實際上就是異步調(diào)用)?的需求,在實現(xiàn)上利用scheduler的異步調(diào)度函數(shù):scheduleCallback,將執(zhí)行useEffect的動作作為一個任務(wù)去調(diào)度,這個任務(wù)會異步調(diào)用。
          commit階段和useEffect真正扯上關(guān)系的有三個地方:commit階段的開始、beforeMutationlayout,涉及到異步調(diào)度的是后面兩個。
          function?commitRootImpl(root,?renderPriorityLevel)?{
          ??//?進入commit階段,先執(zhí)行一次之前未執(zhí)行的useEffect
          ??do?{
          ????flushPassiveEffects();
          ??}?while?(rootWithPendingPassiveEffects?!==?null);

          ??...

          ??do?{
          ????try?{
          ??????// beforeMutation階段的處理函數(shù):commitBeforeMutationEffects內(nèi)部,
          ??????//?異步調(diào)度useEffect

          ??????commitBeforeMutationEffects();
          ????}?catch?(error)?{
          ??????...
          ????}
          ??}?while?(nextEffect?!==?null);

          ??...

          ??const?rootDidHavePassiveEffects?=?rootDoesHavePassiveEffects;

          ??if?(rootDoesHavePassiveEffects)?{
          ????//?重點,記錄有副作用的effect
          ????rootWithPendingPassiveEffects?=?root;
          ??}
          }
          這三個地方去執(zhí)行或者調(diào)度useEffect有什么用意呢?我們分別來看。
          • commit開始,先執(zhí)行一下useEffect:這和useEffect異步調(diào)度的特點有關(guān),它以一般的優(yōu)先級被調(diào)度,這就意味著一旦有更高優(yōu)先級的任務(wù)進入到commit階段,上一次任務(wù)的useEffect還沒得到執(zhí)行。所以在本次更新開始前,需要先將之前的useEffect都執(zhí)行掉,以保證本次調(diào)度的useEffect都是本次更新產(chǎn)生的。
          • beforeMutation階段異步調(diào)度useEffect:這個是實打?qū)嵉蒯槍ffectList上有副作用的節(jié)點,去異步調(diào)度useEffect。
          function?commitBeforeMutationEffects()?{
          ??while?(nextEffect?!==?null)?{

          ????...

          ????if?((flags?&?Passive)?!==?NoFlags)?{
          ??????//?如果fiber節(jié)點上的flags存在Passive調(diào)度useEffect
          ??????if?(!rootDoesHavePassiveEffects)?{
          ????????rootDoesHavePassiveEffects?=?true;
          ????????scheduleCallback(NormalSchedulerPriority,?()?=>?{
          ??????????flushPassiveEffects();
          ??????????return?null;
          ????????});
          ??????}
          ????}
          ????nextEffect?=?nextEffect.nextEffect;
          ??}
          }
          因為rootDoesHavePassiveEffects的限制,只會發(fā)起一次useEffect調(diào)度,相當(dāng)于用一把鎖鎖住調(diào)度狀態(tài),避免發(fā)起多次調(diào)度。
          • layout階段填充effect執(zhí)行數(shù)組:真正useEffect執(zhí)行的時候,實際上是先執(zhí)行上一次effect的銷毀,再執(zhí)行本次effect的創(chuàng)建。React用兩個數(shù)組來分別存儲銷毀函數(shù)和 創(chuàng)建函數(shù),這兩個數(shù)組的填充就是在layout階段,到時候循環(huán)釋放執(zhí)行兩個數(shù)組中的函數(shù)即可。

          function?commitLifeCycles(
          ??finishedRoot:?FiberRoot,
          ??current:?Fiber?|?null,
          ??finishedWork:?Fiber,
          ??committedLanes:?Lanes,
          ):?void?
          {
          ??switch?(finishedWork.tag)?{
          ????case?FunctionComponent:
          ????case?ForwardRef:
          ????case?SimpleMemoComponent:
          ????case?Block:?{

          ??????...

          ??????//?layout階段填充effect執(zhí)行數(shù)組
          ??????schedulePassiveEffects(finishedWork);
          ??????return;
          ????}
          }
          在調(diào)用schedulePassiveEffects填充effect執(zhí)行數(shù)組時,有一個重要的地方就是只在包含HasEffect的effectTag的時候,才將effect放到數(shù)組內(nèi),這一點保證了依賴項有變化再去處理effect。也就是:如果前后依賴未變,則effect的tag就賦值為傳入的hookFlags,否則,在tag中加入HookHasEffect標(biāo)志位。正是因為這樣,在處理effect鏈表時才可以只處理依賴變化的effect,use(Layout)Effect才可以根據(jù)它的依賴變化情況來決定是否執(zhí)行回調(diào)。


          schedulePassiveEffects的實現(xiàn):

          function?schedulePassiveEffects(finishedWork:?Fiber)?{
          ??//?獲取到函數(shù)組件的updateQueue
          ??const?updateQueue:?FunctionComponentUpdateQueue?|?null?=?(finishedWork.updateQueue:?any);
          ??//?獲取effect鏈表
          ??const?lastEffect?=?updateQueue?!==?null???updateQueue.lastEffect?:?null;
          ??if?(lastEffect?!==?null)?{
          ????const?firstEffect?=?lastEffect.next;
          ????let?effect?=?firstEffect;
          ????//?循環(huán)effect鏈表
          ????do?{
          ??????const?{next,?tag}?=?effect;
          ??????if?(
          ????????(tag?&?HookPassive)?!==?NoHookEffect?&&
          ????????(tag?&?HookHasEffect)?!==?NoHookEffect
          ??????)?{
          ????????//?當(dāng)effect的tag含有HookPassive和HookHasEffect時,向數(shù)組中push?effect
          ????????enqueuePendingPassiveHookEffectUnmount(finishedWork,?effect);
          ????????enqueuePendingPassiveHookEffectMount(finishedWork,?effect);
          ??????}
          ??????effect?=?next;
          ????}?while?(effect?!==?firstEffect);
          ??}
          }
          在調(diào)用enqueuePendingPassiveHookEffectUnmountenqueuePendingPassiveHookEffectMount填充數(shù)組的時候,還會再異步調(diào)度一次useEffect,但這與beforeMutation的調(diào)度是互斥的,一旦之前調(diào)度過,就不會再調(diào)度了,同樣是rootDoesHavePassiveEffects起的作用。


          執(zhí)行effect

          此時我們已經(jīng)知道,effect得以被處理是因為之前的調(diào)度以及effect數(shù)組的填充。現(xiàn)在到了最后的步驟,執(zhí)行effect的destroy和create。過程就是先循環(huán)待銷毀的effect數(shù)組,再循環(huán)待創(chuàng)建的effect數(shù)組,這一過程發(fā)生在flushPassiveEffectsImpl函數(shù)中。循環(huán)的時候每個兩項去effect是由于奇數(shù)項存儲的是當(dāng)前的fiber。

          function?flushPassiveEffectsImpl()?{
          ??//?先校驗,如果root上沒有?Passive?efectTag的節(jié)點,則直接return
          ??if?(rootWithPendingPassiveEffects?===?null)?{
          ????return?false;
          ??}

          ??...

          ??//?執(zhí)行effect的銷毀
          ??const?unmountEffects?=?pendingPassiveHookEffectsUnmount;
          ??pendingPassiveHookEffectsUnmount?=?[];
          ??for?(let?i?=?0;?i?2)?{
          ????const?effect?=?((unmountEffects[i]:?any):?HookEffect);
          ????const?fiber?=?((unmountEffects[i?+?1]:?any):?Fiber);
          ????const?destroy?=?effect.destroy;
          ????effect.destroy?=?undefined;

          ????if?(typeof?destroy?===?'function')?{
          ??????try?{
          ????????destroy();
          ??????}?catch?(error)?{
          ????????captureCommitPhaseError(fiber,?error);
          ??????}
          ????}
          ??}

          ??//?再執(zhí)行effect的創(chuàng)建
          ??const?mountEffects?=?pendingPassiveHookEffectsMount;
          ??pendingPassiveHookEffectsMount?=?[];
          ??for?(let?i?=?0;?i?2)?{
          ????const?effect?=?((mountEffects[i]:?any):?HookEffect);
          ????const?fiber?=?((mountEffects[i?+?1]:?any):?Fiber);
          ????try?{
          ??????const?create?=?effect.create;
          ??????effect.destroy?=?create();
          ????}?catch?(error)?{

          ??????captureCommitPhaseError(fiber,?error);
          ????}
          ??}

          ??...

          ??return?true;
          }


          useLayoutEffect的同步執(zhí)行

          useLayoutEffect在執(zhí)行的時候,也是先銷毀,再創(chuàng)建。和useEffect不同的是這兩者都是同步執(zhí)行的,前者在mutation階段執(zhí)行,后者在layout階段執(zhí)行。與useEffect不同的是,它不用數(shù)組去存儲銷毀和創(chuàng)建函數(shù),而是直接操作fiber.updateQueue
          卸載上一次的effect,發(fā)生在mutation階段
          //?調(diào)用卸載layout effect的函數(shù),傳入layout有關(guān)的effectTag和說明effect有變化的effectTag:HookLayout | HookHasEffect
          commitHookEffectListUnmount(HookLayout?|?HookHasEffect,?finishedWork);

          function?commitHookEffectListUnmount(tag:?number,?finishedWork:?Fiber)?{
          ??//?獲取updateQueue
          ??const?updateQueue:?FunctionComponentUpdateQueue?|?null?=?(finishedWork.updateQueue:?any);
          ??const?lastEffect?=?updateQueue?!==?null???updateQueue.lastEffect?:?null;

          ??//?循環(huán)updateQueue上的effect鏈表
          ??if?(lastEffect?!==?null)?{
          ????const?firstEffect?=?lastEffect.next;
          ????let?effect?=?firstEffect;
          ????do?{
          ??????if?((effect.tag?&?tag)?===?tag)?{
          ????????//?執(zhí)行銷毀
          ????????const?destroy?=?effect.destroy;
          ????????effect.destroy?=?undefined;
          ????????if?(destroy?!==?undefined)?{
          ??????????destroy();
          ????????}
          ??????}
          ??????effect?=?effect.next;
          ????}?while?(effect?!==?firstEffect);
          ??}
          }

          執(zhí)行本次的effect創(chuàng)建,發(fā)生在layout階段

          //?調(diào)用創(chuàng)建layout?effect的函數(shù)
          commitHookEffectListMount(HookLayout?|?HookHasEffect,?finishedWork);

          function?commitHookEffectListMount(tag:?number,?finishedWork:?Fiber)?{
          ??const?updateQueue:?FunctionComponentUpdateQueue?|?null?=?(finishedWork.updateQueue:?any);
          ??const?lastEffect?=?updateQueue?!==?null???updateQueue.lastEffect?:?null;
          ??if?(lastEffect?!==?null)?{
          ????const?firstEffect?=?lastEffect.next;
          ????let?effect?=?firstEffect;
          ????do?{
          ??????if?((effect.tag?&?tag)?===?tag)?{
          ????????//?創(chuàng)建
          ????????const?create?=?effect.create;
          ????????effect.destroy?=?create();
          ??????}
          ??????effect?=?effect.next;
          ????}?while?(effect?!==?firstEffect);
          ??}
          }

          文章轉(zhuǎn)載于 https://www.cnblogs.com/cczlovexw/p/16172130.html

          四、總結(jié)

          useEffect和useLayoutEffect作為組件的副作用,本質(zhì)上是一樣的。共用一套結(jié)構(gòu)來存儲effect鏈表。整體流程上都是先在render階段,生成effect,并將它們拼接成鏈表,存到fiber.updateQueue上,最終帶到commit階段被處理。

          他們彼此的區(qū)別只是最終的執(zhí)行時機不同,一個異步一個同步,這使得useEffect不會阻塞渲染,而useLayoutEffect會阻塞渲染。

          五、最后

          ?在我們閱讀完官方文檔后,我們一定會進行更深層次的學(xué)習(xí),比如看下框架底層是如何運行的,以及源碼的閱讀。
          ? ? 這里廣東靚仔給下一些小建議:
          • 在看源碼前,我們先去官方文檔復(fù)習(xí)下框架設(shè)計理念、源碼分層設(shè)計
          • 閱讀下框架官方開發(fā)人員寫的相關(guān)文章
          • 借助框架的調(diào)用棧來進行源碼的閱讀,通過這個執(zhí)行流程,我們就完整的對源碼進行了一個初步的了解
          • 接下來再對源碼執(zhí)行過程中涉及的所有函數(shù)邏輯梳理一遍

          關(guān)注我,一起攜手進階

          歡迎關(guān)注前端早茶,與廣東靚仔共同進階~

          瀏覽 35
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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免费在线 成人A片在线观看 | 无码国产激情视频 | 一级一区| 自拍亚洲| 人人操操 |