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

          「從0實(shí)現(xiàn)React18系列」Reconciler架構(gòu)的雙緩存樹實(shí)現(xiàn)原理

          共 10078字,需瀏覽 21分鐘

           ·

          2023-07-19 17:33

          從0實(shí)現(xiàn)React核心模塊系列文章:

          「1」. 自己動(dòng)手實(shí)現(xiàn)一個(gè)JSX轉(zhuǎn)換

          「2」. Fiber架構(gòu)的實(shí)現(xiàn)原理

          前言

          通過(guò)上一篇文章的學(xué)習(xí),了解了Fiber是什么,知道了Fiber節(jié)點(diǎn)可以保存對(duì)應(yīng)的DOM節(jié)點(diǎn)。Fiber節(jié)點(diǎn)構(gòu)成的Fiber Tree會(huì)對(duì)應(yīng)DOM Tree

          前面也提到Fiber是一種新的調(diào)和算法,那么它是如何更新DOM節(jié)點(diǎn)的呢?

          單個(gè)節(jié)點(diǎn)的創(chuàng)建更新流程

          對(duì)于同一個(gè)節(jié)點(diǎn),React 會(huì)比較這個(gè)節(jié)點(diǎn)的ReactElementFiberNode,生成子FiberNode。并根據(jù)比較的結(jié)果生成不同標(biāo)記(插入、刪除、移動(dòng)...),對(duì)應(yīng)不同宿主環(huán)境API的執(zhí)行。

          92f2723353885bce2c5fa28d0c9957e7.webp

          根據(jù)上面的Reconciler的工作流程,舉一個(gè)例子:

          比如:

          mount階段,掛載<div></div>

          1. 先通過(guò)jsx("div")生成 React Element <div></div>
          2. 生成的對(duì)應(yīng)的fiberNode為null(由于是由于是掛載階段,React還未構(gòu)建組件樹)
          3. 生成子fiberNode(實(shí)際上就是這個(gè)div的fiber節(jié)點(diǎn))
          4. 生成Placement標(biāo)記

          <div></div>更新為<p></p>

          update階段,更新將<div></div>更新為<p></p>

          1. 先通過(guò)jsx("p")生成 React Element <p></p>
          2. p與對(duì)應(yīng)的fiberNode作比較(FiberNode {type: 'div'})
          3. 生成子fiberNode為null
          4. 生成對(duì)應(yīng)標(biāo)記Delement Placement

          用一張圖解釋上面的流程:

          1562645e86fcc213baccbe2d7f6e7adf.webp

          當(dāng)所有的ReactElement比較完后,會(huì)生成一顆fiberNode Tree,一共會(huì)存在兩棵fiberNode Tree

          • current:與視圖中真實(shí)UI對(duì)應(yīng)的fiberNode樹;
          • workInProgress:觸發(fā)更新后,正在reconciler中計(jì)算的fiberNode Tree(用于下一次的視圖更新,在下一次視圖更新后,會(huì)變成current Tree);

          這就是React中的"雙緩存樹"技術(shù)。

          什么是"雙緩存"?

          雙緩存技術(shù)是一種計(jì)算機(jī)圖形學(xué)中用于減少屏幕閃爍和提高渲染性能的技術(shù)。

          就好像你是一個(gè)畫家,你需要在一個(gè)畫布上繪制一幅畫。在沒(méi)有雙緩存技術(shù)的情況下,你會(huì)直接在畫布上作畫。當(dāng)你繪制一條線或一個(gè)形狀時(shí),觀眾會(huì)立即看到這個(gè)過(guò)程。如果你的繪畫速度較慢,觀眾可能會(huì)看到畫面的閃爍和變化,這會(huì)導(dǎo)致視覺(jué)上的不舒適。

          引入雙緩存技術(shù)就好比你有兩個(gè)畫布:一個(gè)是主畫布,觀眾可以看到它;另一個(gè)是隱藏畫布,觀眾看不到它。在這種情況下,你會(huì)在隱藏畫布上進(jìn)行繪畫。當(dāng)你完成一個(gè)階段性的繪制任務(wù)后,你將隱藏畫布上的圖像瞬間復(fù)制到主畫布上。觀眾只能看到主畫布上的圖像,而看不到隱藏畫布上的繪制過(guò)程。這樣,即使你的繪畫速度較慢,觀眾也不會(huì)看到畫面的閃爍和變化,從而獲得更流暢的視覺(jué)體驗(yàn)。

          使用雙緩存技術(shù)時(shí),計(jì)算機(jī)會(huì)在一個(gè)隱藏的緩沖區(qū)(后臺(tái)緩沖區(qū))上進(jìn)行繪制,然后將繪制好的圖像一次性復(fù)制到屏幕上(前臺(tái)緩沖區(qū))。這樣可以減少屏幕閃爍,并提高渲染性能。

          這種在內(nèi)存中構(gòu)建并直接替換的技術(shù)叫作雙緩存。

          React 中使用"雙緩存"來(lái)完成Fiber Tree的構(gòu)建與替換,對(duì)應(yīng)著DOM Tree的創(chuàng)建于與更新。

          雙緩存Fiber樹

          Fiber架構(gòu)中同時(shí)存在兩棵Fiber Tree,一顆是"真實(shí)UI對(duì)應(yīng)的 Fiber Tree"可以理解為前緩沖區(qū)。另一課是"正在內(nèi)存中構(gòu)建的 Fiber Tree"可以理解為后緩沖區(qū),這里值宿主環(huán)境(比如瀏覽器)。

          當(dāng)前屏幕上顯示內(nèi)容對(duì)應(yīng)的Fiber樹稱為current Fiber樹,正在內(nèi)存中構(gòu)建的Fiber樹稱為workInProgress Fiber樹。

          current Fiber樹中的Fiber節(jié)點(diǎn)被稱為current fiber,workInProgress Fiber樹中的Fiber節(jié)點(diǎn)被稱為workInProgress fiber,他們通過(guò)alternate屬性連接。

          雙緩存樹一個(gè)顯著的特點(diǎn)就是兩棵樹之間會(huì)互相切換,通過(guò)alternate屬性連接。

                
                currentFiber.alternate?===?workInProgressFiber;
          workInProgressFiber.alternate?===?currentFiber;

          雙緩存樹切換的規(guī)則

          React應(yīng)用的根節(jié)點(diǎn)通過(guò)current指針在不同F(xiàn)iber樹的HostRootFiber根節(jié)點(diǎn)(ReactDOM.render創(chuàng)建的根節(jié)點(diǎn))間切換。

          • 在 mount時(shí)(首次渲染),會(huì)根據(jù)jsx方法返回的React Element構(gòu)建Fiber對(duì)象,形成Fiber樹;
            • 然后這棵Fiber樹會(huì)作為current Fiber應(yīng)用到真實(shí)DOM上
          • 在 update時(shí)(狀態(tài)更新),會(huì)根據(jù)狀態(tài)變更后的React Elementcurrent Fiber作對(duì)比形成新的workInProgress Fiber
            • 即當(dāng)workInProgress Fiber樹構(gòu)建完成交給Renderer(渲染器)渲染在頁(yè)面上后,應(yīng)用根節(jié)點(diǎn)的current指針指向workInProgress Fiber樹
            • 然后workInProgress Fiber切換成current Fiber應(yīng)用到真實(shí)DOM上,這就達(dá)到了更新的目的。

          這一切都是在內(nèi)存中發(fā)生的,從而減少了對(duì)DOM的直接操作。

          每次狀態(tài)更新都會(huì)產(chǎn)生新的workInProgress Fiber樹,通過(guò)currentworkInProgress的替換,完成DOM更新,這就是React中用的雙緩存樹切換規(guī)則

          Renderer 是一個(gè)與特定宿主環(huán)境(如瀏覽器 DOM、服務(wù)器端渲染、React Native 等)相關(guān)的模塊。Renderer 負(fù)責(zé)將 React 組件樹轉(zhuǎn)換為特定宿主環(huán)境下的實(shí)際 UI。從而使 React 能夠在多個(gè)平臺(tái)上運(yùn)行。

          上面的語(yǔ)言可能有些枯燥,我們來(lái)畫個(gè)圖演示一下。

          比如有下面這樣一段代碼,點(diǎn)擊元素把div切換成p元素:

                
                function?App()?{
          ??const?[elementType,?setElementType]?=?useState('div');

          ??const?handleClick?=?()?=>?{
          ????setElementType(prevElementType?=>?{
          ??????return?prevElementType?===?'div'???'p'?:?'div';
          ????})
          ??}

          ??//?根據(jù)?elementType?的值動(dòng)態(tài)創(chuàng)建對(duì)應(yīng)的元素
          ??const?Element?=?elementType;

          ??return?(
          ????<div>
          ??????<Element?onClick={handleClick}>
          ????????點(diǎn)擊我切換?div?和?p?標(biāo)簽
          ??????</Element>
          ????</div>

          ??)
          }

          const?root?=?document.querySelector("#root");
          ReactDOM.createRoot(root).render(<App?/>);
          1b668ba2e8c488d2d35f1c3a1aca8bb3.webp

          接下來(lái),我們分別從 mount(首次渲染)和 update(更新)兩個(gè)角度講解 Fiber 架構(gòu)的工作原理。

          mount 時(shí) Fiber Tree的構(gòu)建

          mount 時(shí)有兩種情況:

          1. 整個(gè)應(yīng)用的首次渲染,這種情況發(fā)生首次進(jìn)入頁(yè)面時(shí)
          2. 某個(gè)組件的首次渲染,當(dāng) isShow 為 true時(shí),Btn 組件進(jìn)入 mount 首次渲染流程。
                
                {isShow???<Btn?/>?:?null}

          假如有這樣一段代碼:

                
                function?App()?{
          ??const?[num,?add]?=?useState(0);
          ??return?(
          ????<p?onClick={()?=>?add(num?+?1)}>{num}</p>
          ??)
          }

          const?root?=?document.querySelector("#root");
          ReactDOM.createRoot(root).render(<App?/>)

          mount 時(shí)上面的Fiber樹構(gòu)建過(guò)程如下:

          1. 首次執(zhí)行ReactDOM.createRoot(root)會(huì)創(chuàng)建fiberRootNode
          2. 接著執(zhí)行到render(<App />)時(shí)會(huì)創(chuàng)建HostRootFiber,實(shí)際上它是一個(gè)HostRoot節(jié)點(diǎn)

          fiberRootNode 是整個(gè)應(yīng)用的根節(jié)點(diǎn),HostRootFiber<App /> 所在組件樹的根節(jié)點(diǎn)

          1. HostRootFiber開始,以DFS(深度優(yōu)先搜索)的的順序遍歷子節(jié)點(diǎn),以及生成對(duì)應(yīng)的FiberNode;
          2. 在遍歷過(guò)程中,為FiberNode標(biāo)記"代表不同副作用的 flags",以便后續(xù)在宿主環(huán)境中渲染的使用;

          在上面我們之所以要區(qū)分fiberRootNodeHostRootFiber是因?yàn)樵谡麄€(gè)React應(yīng)用程序中開發(fā)者可以多次多次調(diào)用render方法渲染不同的組件樹,它們會(huì)有不同的HostRootFiber,但是整個(gè)應(yīng)用的根節(jié)點(diǎn)只有一個(gè),那就是fiberRootNode。

          執(zhí)行 ReactDOM.createRoot 會(huì)創(chuàng)建如圖所示結(jié)構(gòu):

          cc466043dbb06a58a6f39c6b65b6f6e9.webp

          mount 首屏渲染階段

          由于是首屏渲染階段,頁(yè)面中還沒(méi)有掛載任何DOM節(jié)點(diǎn),所以fiberRootNode.current指向的HostRootFiber沒(méi)有任何子Fiber節(jié)點(diǎn)(即current Fiber樹為空)。

          當(dāng)前僅有一個(gè)HostRootFiber,對(duì)應(yīng)"首屏渲染時(shí)只有根節(jié)點(diǎn)的空白畫面"。

                
                <body>
          ??<div?id="root"></div>
          </body>

          render 生成workInProgress樹階段

          接下來(lái)進(jìn)入render階段,根據(jù)組件返回的JSX在內(nèi)存中依次構(gòu)建創(chuàng)建Fiber節(jié)點(diǎn)并連接在一起構(gòu)建Fiber樹,被稱為workInProgress Fiber樹。

          在構(gòu)建workInProgress Fiber樹時(shí)會(huì)嘗試復(fù)用current Fiber樹中已有的Fiber節(jié)點(diǎn)內(nèi)的屬性,(在首屏渲染時(shí),只有HostRootFiber),也可以理解為首屏渲染時(shí),它以自己的身份生成了一個(gè)workInProgress 樹只不過(guò)還是HostRootFiberHostRootFiber.alternate。

          基于DFS(深度優(yōu)先搜索)依次生成的workInProgress節(jié)點(diǎn),并連接起來(lái)構(gòu)成wip 樹的過(guò)程如圖所示:

          2528b77af828d3cfd6b0c8a833f18bb4.webp

          上圖中已構(gòu)建完的workInProgress Fiber樹會(huì)在commit階段被渲染到頁(yè)面。

          commit 階段

          等到頁(yè)面渲染完成時(shí),workInProgress Fiber樹會(huì)替換之前的current Fiber樹,進(jìn)而fiberRootNodecurrent指針會(huì)指向新的current Fiber樹

          完成雙緩存樹的切換工作,曾經(jīng)的Wip Fiber樹變?yōu)?code style="font-size:14px;font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;color:rgb(55,125,202);background-color:rgb(255,245,227);">current Fiber樹。

          過(guò)程如圖所示:

          e194e5bf77ac0c02eeae19e72ff8fb27.webp

          update 時(shí) Fiber Tree的更迭

          1. 接下來(lái)我們點(diǎn)擊p節(jié)點(diǎn)觸發(fā)狀態(tài)改變。這會(huì)開啟一次新的render階段并構(gòu)建一課新的workInProgress Fiber樹。

          和mount時(shí)一樣,workInProgress Fiber的創(chuàng)建可以復(fù)用current Fiber樹對(duì)應(yīng)節(jié)點(diǎn)的數(shù)據(jù),這個(gè)決定是否服用的過(guò)程就是Diff算法, 后面章節(jié)會(huì)詳細(xì)講解

          5f9d8286bbe44386fcfefa125034cddc.webp
          1. workInProgress Fiber樹render階段完成構(gòu)建后會(huì)進(jìn)入commit階段渲染到頁(yè)面上。渲染完成后,workInProgress Fiber樹變?yōu)?code style="font-size:14px;font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;color:rgb(55,125,202);background-color:rgb(255,245,227);">current Fiber樹。
          5be7496b444b84ef773365f89fa71612.webp

          render 階段的流程

          接下來(lái),我們來(lái)看看用原理,在源碼中它是如何實(shí)現(xiàn)的。

          Reconciler工作的階段在 React 內(nèi)部被稱為 render 階段,ClassComponent 的render函數(shù)、Function Component函數(shù)本身也都在 render 階段被調(diào)用。

          根據(jù)Scheduler調(diào)度的結(jié)果不同,render階段可能開始于performSyncWorkOnRootperformConcurrentWorkOnRoot方法的調(diào)用。

          也就是說(shuō)React在執(zhí)行render階段的初期會(huì)依賴于Scheduler(調(diào)度器)的結(jié)果來(lái)判斷執(zhí)行哪個(gè)方法,比如Scheduler(調(diào)度器)會(huì)根據(jù)任務(wù)的優(yōu)先級(jí)選擇執(zhí)行performSyncWorkOnRootperformConcurrentWorkOnRoot方法。這取決于任務(wù)的類型和優(yōu)先級(jí)。同步任務(wù)通常具有較高優(yōu)先級(jí),需要立即執(zhí)行,而并發(fā)任務(wù)會(huì)在空閑時(shí)間段執(zhí)行以避免阻塞主線程。

          這里補(bǔ)充一下,調(diào)度器可能的執(zhí)行結(jié)果,以用來(lái)判斷執(zhí)行什么入口函數(shù):

          如果不知道調(diào)度器的執(zhí)行結(jié)構(gòu)都有哪幾類,可以跳過(guò)這段代碼向下看:

          現(xiàn)在還不需要學(xué)習(xí)這兩個(gè)方法,只需要知道在這兩個(gè)方法中會(huì)調(diào)用 performUnitOfWork方法就好。

                
                //?performSyncWorkOnRoot會(huì)調(diào)用該方法
          function?workLoopSync()?{
          ??while?(workInProgress?!==?null)?{
          ????performUnitOfWork(workInProgress);
          ??}
          }

          //?performConcurrentWorkOnRoot會(huì)調(diào)用該方法
          function?workLoopConcurrent()?{
          ??while?(workInProgress?!==?null?&&?!shouldYield())?{
          ????performUnitOfWork(workInProgress);
          ??}
          }

          可以看到,它們唯一的區(qū)別就是是否會(huì)調(diào)用shouldYield。如果當(dāng)前瀏覽器幀沒(méi)有剩余時(shí)間,shouldYield會(huì)終止循環(huán),直到瀏覽器有空閑時(shí)間再繼續(xù)遍歷。

          也就說(shuō)當(dāng)更新正在進(jìn)行時(shí),如果有 "更高優(yōu)先級(jí)的更新" 產(chǎn)生,則會(huì)終端當(dāng)前更新,優(yōu)先處理高優(yōu)先級(jí)更新。

          高優(yōu)先級(jí)的更新比如:"鼠標(biāo)懸停","文本框輸入"等用戶更易感知的操作。

          workInProgress代表當(dāng)前正在工作的一個(gè)fiberNode,它是一個(gè)全局的指針,指向當(dāng)前正在工作的 fiberNode,一般是workInProgress。

          performUnitOfWork方法會(huì)創(chuàng)建下一個(gè)Fiber節(jié)點(diǎn),并賦值給workInProgress,并將workInProgress與已經(jīng)創(chuàng)建好的Fiber節(jié)點(diǎn)連接起來(lái)構(gòu)成Fiber樹。

          這里為什么指向的是 workInProgress 呢? 因?yàn)樵诿看武秩靖聲r(shí),即將展示到界面上的是 workInProgress 樹,只有在首屏渲染的時(shí)候它才為空。

          render階段流程概覽

          Fiber Reconciler是從Stack Reconciler重構(gòu)而來(lái),通過(guò)遞歸遍歷的方式實(shí)現(xiàn)可中斷的遞歸。 因?yàn)榭梢园?code style="font-size:14px;font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;color:rgb(55,125,202);background-color:rgb(255,245,227);">performUnitOfWork方法分為兩部分:"遞"和"歸"。

          "遞" 階段會(huì)從 HostRootFiber開始向下以 DFS 的方式遍歷,為遍歷到的每個(gè)fiberNode執(zhí)行beginWork方法。該方法會(huì)根據(jù)傳入的fiberNode創(chuàng)建下一級(jí)fiberNode。

          當(dāng)遍歷到葉子元素(不包含子fiberNode)時(shí),performUnitOfWork就會(huì)進(jìn)入 "歸" 的階段。

          "歸" 階段會(huì)調(diào)用completeWork方法處理fiberNode。當(dāng)某個(gè)fiberNode執(zhí)行完complete方法后,如果其存在兄弟fiberNode(fiberNode.sibling !== null),會(huì)進(jìn)入其兄弟fiber的"遞階段"。如果不存在兄弟fiberNode,會(huì)進(jìn)入父fiberNode"歸" 階段。

          遞階段和歸階段會(huì)交錯(cuò)執(zhí)行直至HostRootFiber的"歸"階段。到此,render階段的工作就結(jié)束了。

          舉一個(gè)例子:

                
                function?App()?{
          ??return?(
          ????<div>
          ??????<p>1229</p>
          ??????jasonshu
          ????</div>

          ??)
          }

          const?root?=?document.querySelector("#root");
          ReactDOM.createRoot(root).render(<App?/>);

          當(dāng)執(zhí)行完深度優(yōu)先搜索之后形成的workInProgress樹

          a7f329e78aa0d490251fadff327bb1f8.webp

          圖中的數(shù)組是遍歷過(guò)程中的順序,可以看到,遍歷的過(guò)程中會(huì)從應(yīng)用的根節(jié)點(diǎn)RootFiberNode開始,依次執(zhí)行beginWorkcompleteWork,最后形成一顆Fiber樹,每個(gè)節(jié)點(diǎn)以child和return項(xiàng)鏈。

          注意:當(dāng)遍歷到只有一個(gè)子文本節(jié)點(diǎn)的Fiber時(shí),該Fiber節(jié)點(diǎn)的子節(jié)點(diǎn)不會(huì)執(zhí)行beginWork和completeWork,如圖中的"jasonshu"文本節(jié)點(diǎn)。這是react的一種優(yōu)化手段

          剛剛提到:workInProgress代表當(dāng)前正在工作的一個(gè)fiberNode,它是一個(gè)全局的指針,指向當(dāng)前正在工作的 fiberNode,一般是workInProgress。

                
                //?該函數(shù)用于調(diào)度和執(zhí)行?FiberNode?樹的更新和渲染過(guò)程
          //?該函數(shù)的作用是處理?React?程序中更新請(qǐng)求,計(jì)算?FiberNode?樹中的每個(gè)節(jié)點(diǎn)的變化,并把這些變化同步到瀏覽器的DOM中
          function?workLoop()?{
          ??while?(workInProgress?!==?null)?{
          ????//?開始執(zhí)行每個(gè)工作單元的工作
          ????performUmitOfWork(workInProgress);
          ??}
          }

          知道了beginWorkcompleteWork它們是怎樣的流程后,我們?cè)賮?lái)看它是如何實(shí)現(xiàn)的:

          這段代碼主要計(jì)算FiberNode節(jié)點(diǎn)的變化,更新workInProgress,beginWork函數(shù)的最初運(yùn)行也是在下面這個(gè)函數(shù)中,同時(shí)它也完成遞和歸兩個(gè)階段的操作。

                
                //?在這個(gè)函數(shù)中,React?會(huì)計(jì)算?FiberNode?節(jié)點(diǎn)的變化,并更新?workInProgress
          function?performUmitOfWork(fiber:?FiberNode)?{
          ??//?如果有子節(jié)點(diǎn),就一直遍歷子節(jié)點(diǎn)
          ??const?next?=?beginWork(fiber);
          ??//?遞執(zhí)行完之后,需要更新下工作單元的props
          ??fiber.memoizedProps?=?fiber.pendingProps;

          ??//?沒(méi)有子節(jié)點(diǎn)的?FiberNode?了,代表遞歸到最深層了。
          ??if?(next?===?null)?{
          ????completeUnitOfWork(fiber);
          ??}?else?{
          ??//?如果有子節(jié)點(diǎn)的?FiberNode,則更新子節(jié)點(diǎn)為新的?fiberNode?繼續(xù)執(zhí)行
          ????workInProgress?=?next;
          ??}
          }

          在下面的函數(shù)中主要進(jìn)行的操作:

                
                //?主要進(jìn)行歸的過(guò)程,向上遍歷父節(jié)點(diǎn)以及兄弟,更新它們節(jié)點(diǎn)的變化,并更新?workInProgress
          function?completeUnitOfWork(fiber:?FiberNode)?{
          ??let?node:?FiberNode?|?null?=?fiber;

          ??do?{
          ????//?歸:沒(méi)有子節(jié)點(diǎn)之后開始向上遍歷父節(jié)點(diǎn)
          ????completeWork(node);
          ????const?sibling?=?node.sibling;
          ????if?(sibling?!==?null)?{
          ??????//?有兄弟節(jié)點(diǎn)時(shí),將指針指到兄弟節(jié)點(diǎn)
          ??????workInProgress?=?sibling;
          ??????return;
          ????}
          ????//?兄弟節(jié)點(diǎn)不存在時(shí),遞歸應(yīng)該繼續(xù)往上指到父親節(jié)點(diǎn)
          ????node?=?node.return;
          ????workInProgress?=?node;
          ??}?while?(node?!==?null);
          }

          到此,Reconciler的工作架構(gòu)架子我們就搭完了。

          接下來(lái)我們來(lái)講在構(gòu)建過(guò)程中每個(gè)Fiber節(jié)點(diǎn)具體是如何創(chuàng)建的呢?在下一篇會(huì)詳細(xì)講解beginWorkcompleteWork是如何實(shí)現(xiàn)的?會(huì)正式進(jìn)入render階段的實(shí)現(xiàn)了。


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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  免费一区区三区四区 | 国产精品无码插逼 | 免费观看一区二区三区 | 国产婷婷精品视频 | 天天日天天操天天射 |