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>

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

        共 9676字,需瀏覽 20分鐘

         ·

        2022-04-30 12:30

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

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

        一、前言

        在React項目中,我們使用hook開發(fā)項目,有時候會由于對hook的執(zhí)行機制、原理不是很清楚,遇到一些"難以解決的"問題。
        其實當我們遇到問題的時候,我們應(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)度,當頁面渲染完成后再去執(zhí)行,不會阻塞頁面渲染。后者是在commit階段新的DOM準備完成,但還未渲染到屏幕之前,同步執(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標志位。正是因為這樣,在處理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,它的職責很單純,就是創(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)建鏈表做準備
        ??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)度

        componentDidMount、componentDidUpdate 不同的是,在瀏覽器完成布局與繪制之后,傳給 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階段的開始、beforeMutation、layout,涉及到異步調(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)度,相當于用一把鎖鎖住調(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標志位。正是因為這樣,在處理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
        ??????)?{
        ????????//?當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ù)項存儲的是當前的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會阻塞渲染。

        五、最后

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

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

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

        瀏覽 35
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        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>
            美女一级片 | 亚洲中文视频在线 | 吃奶边添边摸边做边爱文章 | 91国产在线看 | 噢美一级片 | 亚洲小鲜肉gay网站免费 免费观看黄色小视频 | 97资源中心 | 亚洲三级AV | 电影性呻吟| 波多野结衣伦理片在线观看 |