<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 Hooks源碼解析!

          共 14389字,需瀏覽 29分鐘

           ·

          2022-03-08 09:28

          前言

          從React Hooks發(fā)布以來,整個社區(qū)都以積極的態(tài)度去擁抱它、學習它。期間也涌現了很多關于React Hooks 源碼解析的文章。本文就以筆者自己的角度來寫一篇屬于自己的文章吧。希望可以深入淺出、圖文并茂的幫助大家對React Hooks的實現原理進行學習與理解。本文將以文字、代碼、圖畫的形式來呈現內容。主要對常用Hooks中的 useState、useReducer、useEffect 進行學習,盡可能的揭開Hooks的面紗。

          使用Hooks時的疑惑

          Hooks的面世讓我們的Function Component逐步擁有了對標Class Component的特性,比如私有狀態(tài),生命周期函數等。useState與useReducer這兩個Hooks讓我們可以在 Function Component里使用到私有狀態(tài)。而useState其實就是閹割版的useReducer,這也是我那它們兩個放在一起講的原因。應用一下官方的例子:

          function?PersionInfo?({initialAge,initialName})?{
          ??const?[age,?setAge]?=?useState(initialAge);
          ??const?[name,?setName]?=?useState(initialName);
          ??return?(
          ????<>
          ??????Age:?{age},?Name:?{name}
          ???????setAge(age?+?1)}>Growing?up
          ????
          ??);
          }

          useState 我們可以初始化一個私有狀態(tài),它會返回這個狀態(tài)的最新值和一個用來更新狀態(tài)的方法。而useReducer則是針對更復雜的狀態(tài)管理場景:

          const?initialState?=?{age:?0,?name:?'Dan'};

          function?reducer(state,?action)?{
          ??switch?(action.type)?{
          ????case?'increment':
          ??????return?{...state,?age:?state.age?+?action.age};
          ????case?'decrement':
          ??????return?{...state,?age:?state.age?-?action.age};
          ????default:
          ??????throw?new?Error();
          ??}
          }
          function?PersionInfo()?{
          ??const?[state,?dispatch]?=?useReducer(reducer,?initialState);
          ??return?(
          ????<>
          ??????Age:?{state.age},?Name:?{state.name}
          ???????dispatch({type:?'decrement',?age:?1})}>-
          ???????dispatch({type:?'increment',?age:?1})}>+
          ????
          ??);
          }

          同樣也是返回當前最新的狀態(tài),并返回一個用來更新數據的方法。在使用這兩個方法的時候也許我們會想過這樣的問題:

          ??const?[age,?setAge]?=?useState(initialAge);
          ??const?[name,?setName]?=?useState(initialName);

          React內部是怎么區(qū)分這兩個狀態(tài)的呢?

          Function Component 不像 Class Component那樣可以將私有狀態(tài)掛載到類實例中并通過對應的key來指向對應的狀態(tài),而且每次的頁面的刷新或者說組件的重新渲染都會使得 Function 重新執(zhí)行一遍。所以React中必定有一種機制來區(qū)分這些Hooks。

          ?const?[age,?setAge]?=?useState(initialAge);
          ?//?或
          ?const?[state,?dispatch]?=?useReducer(reducer,?initialState);

          另一個問題就是React是如何在每次重新渲染之后都能返回最新的狀態(tài)?

          Class Component因為自身的特點可以將私有狀態(tài)持久化的掛載到類實例上,每時每刻保存的都是最新的值。而 Function Component 由于本質就是一個函數,并且每次渲染都會重新執(zhí)行。所以React必定擁有某種機制去記住每一次的更新操作,并最終得出最新的值返回。當然我們還會有其他的一些問題,比如這些狀態(tài)究竟存放在哪?為什么只能在函數頂層使用Hooks而不能在條件語句等里面使用Hooks?

          答案盡在源碼之中

          我們先來了解useState以及useReducer的源碼實現,并從中解答我們在使用Hooks時的種種疑惑。首先我們從源頭開始:

          import?React,?{?useState?}?from?'react';

          在項目中我們通常會以這種方式來引入useState方法,被我們引入的這個useState方法是什么樣子的呢?其實這個方法就在源碼 packages/react/src/ReactHook.js 中。

          //?packages/react/src/ReactHook.js
          import?ReactCurrentDispatcher?from?'./ReactCurrentDispatcher';

          function?resolveDispatcher()?{
          ??const?dispatcher?=?ReactCurrentDispatcher.current;
          ??//?...?
          ??return?dispatcher;
          }

          //?我們代碼中引入的useState方法
          export?function?useState(initialState)?{
          ??const?dispatcher?=?resolveDispatcher();
          ??return?dispatcher.useState(initialState)
          }

          從源碼中可以看到,我們調用的其實是 ReactCurrentDispatcher.js 中的dispatcher.useState(),那么我們繼續(xù)前往ReactCurrentDispatcher.js文件:

          import?type?{Dispacther}?from?'react-reconciler/src/ReactFiberHooks';

          const?ReactCurrentDispatcher?=?{
          ??current:?(null:?null?|?Dispatcher),
          };

          export?default?ReactCurrentDispatcher;

          好吧,它繼續(xù)將我們帶向 react-reconciler/src/ReactFiberHooks.js這個文件。那么我們繼續(xù)前往這個文件。

          //?react-reconciler/src/ReactFiberHooks.js
          export?type?Dispatcher?=?{
          ??useState(initialState:?(()?=>?S)?|?S):?[S,?Dispatch>],
          ??useReducer(
          ????reducer:?(S,?A)?=>?S,
          ????initialArg:?I,
          ????init?:?(I)?=>?S,
          ??):?[S,?Dispatch],
          ??useEffect(
          ????create:?()?=>?(()?=>?void)?|?void,
          ????deps:?Array?|?void?|?null,
          ??):?void,
          ??//?其他hooks類型定義
          }

          兜兜轉轉我們終于清楚了React Hooks 的源碼就放 react-reconciler/src/ReactFiberHooks.js 目錄下面。在這里如上圖所示我們可以看到有每個Hooks的類型定義。同時我們也可以看到Hooks的具體實現,大家可以多看看這個文件。首先我們注意到,我們大部分的Hooks都有兩個定義:

          //?react-reconciler/src/ReactFiberHooks.js
          //?Mount?階段Hooks的定義
          const?HooksDispatcherOnMount:?Dispatcher?=?{
          ??useEffect:?mountEffect,
          ??useReducer:?mountReducer,
          ??useState:?mountState,
          ?//?其他Hooks
          };

          //?Update階段Hooks的定義
          const?HooksDispatcherOnUpdate:?Dispatcher?=?{
          ??useEffect:?updateEffect,
          ??useReducer:?updateReducer,
          ??useState:?updateState,
          ??//?其他Hooks
          };

          從這里可以看出,我們的Hooks在Mount階段和Update階段的邏輯是不一樣的。在Mount階段和Update階段他們是兩個不同的定義。我們先來看Mount階段的邏輯。在看之前我們先思考一些問題。React Hooks需要在Mount階段做什么呢?就拿我們的useState和useReducer來說:

          我們一下React的實現,先來看mountState的實現。

          //?react-reconciler/src/ReactFiberHooks.js
          function?mountState?(initialState)?{
          ??//?獲取當前的Hook節(jié)點,同時將當前Hook添加到Hook鏈表中
          ??const?hook?=?mountWorkInProgressHook();
          ??if?(typeof?initialState?===?'function')?{
          ????initialState?=?initialState();
          ??}
          ??hook.memoizedState?=?hook.baseState?=?initialState;
          ??//?聲明一個鏈表來存放更新
          ??const?queue?=?(hook.queue?=?{
          ????last:?null,
          ????dispatch:?null,
          ????lastRenderedReducer,
          ????lastRenderedState,
          ??});
          ??//?返回一個dispatch方法用來修改狀態(tài),并將此次更新添加update鏈表中
          ??const?dispatch?=?(queue.dispatch?=?(dispatchAction.bind(
          ????null,
          ????currentlyRenderingFiber,
          ????queue,
          ??)));
          ??//?返回當前狀態(tài)和修改狀態(tài)的方法?
          ??return?[hook.memoizedState,?dispatch];
          }

          區(qū)分管理Hooks

          關于第一件事,初始化狀態(tài)并返回狀態(tài)和更新狀態(tài)的方法。這個沒有問題,源碼也很清晰利用initialState來初始化狀態(tài),并且返回了狀態(tài)和對應更新方法 return [hook.memoizedState, dispatch]。那么我們來看看React是如何區(qū)分不同的Hooks的,這里我們可以從 mountState 里的 mountWorkInProgressHook方法和Hook的類型定義中找到答案。

          //?react-reconciler/src/ReactFiberHooks.js
          export?type?Hook?=?{
          ??memoizedState:?any,
          ??baseState:?any,
          ??baseUpdate:?Update?|?null,
          ??queue:?UpdateQueue?|?null,
          ??next:?Hook?|?null,??//?指向下一個Hook
          };

          首先從Hook的類型定義中就可以看到,React 對Hooks的定義是鏈表。也就是說我們組件里使用到的Hooks是通過鏈表來聯(lián)系的,上一個Hooks的next指向下一個Hooks。這些Hooks節(jié)點是怎么利用鏈表數據結構串聯(lián)在一起的呢?相關邏輯就在每個具體mount 階段 Hooks函數調用的 mountWorkInProgressHook方法里:

          //?react-reconciler/src/ReactFiberHooks.js
          function?mountWorkInProgressHook():?Hook?{
          ??const?hook:?Hook?=?{
          ????memoizedState:?null,
          ????baseState:?null,
          ????queue:?null,
          ????baseUpdate:?null,
          ????next:?null,
          ??};
          ??if?(workInProgressHook?===?null)?{
          ????//?當前workInProgressHook鏈表為空的話,
          ????//?將當前Hook作為第一個Hook
          ????firstWorkInProgressHook?=?workInProgressHook?=?hook;
          ??}?else?{
          ????//?否則將當前Hook添加到Hook鏈表的末尾
          ????workInProgressHook?=?workInProgressHook.next?=?hook;
          ??}
          ??return?workInProgressHook;
          }

          在mount階段,每當我們調用Hooks方法,比如useState,mountState就會調用mountWorkInProgressHook 來創(chuàng)建一個Hook節(jié)點,并把它添加到Hooks鏈表上。比如我們的這個例子:

          ??const?[age,?setAge]?=?useState(initialAge);
          ??const?[name,?setName]?=?useState(initialName);
          ??useEffect(()?=>?{})

          那么在mount階段,就會生產如下圖這樣的單鏈表:

          返回最新的值

          而關于第三件事,useState和useReducer都是使用了一個queue鏈表來存放每一次的更新。以便后面的update階段可以返回最新的狀態(tài)。每次我們調用dispatchAction方法的時候,就會形成一個新的updata對象,添加到queue鏈表上,而且這個是一個循環(huán)鏈表??梢钥匆幌?dispatchAction 方法的實現:

          //?react-reconciler/src/ReactFiberHooks.js
          //?去除特殊情況和與fiber相關的邏輯
          function?dispatchAction(fiber,queue,action,)?{
          ????const?update?=?{
          ??????action,
          ??????next:?null,
          ????};
          ????//?將update對象添加到循環(huán)鏈表中
          ????const?last?=?queue.last;
          ????if?(last?===?null)?{
          ??????//?鏈表為空,將當前更新作為第一個,并保持循環(huán)
          ??????update.next?=?update;
          ????}?else?{
          ??????const?first?=?last.next;
          ??????if?(first?!==?null)?{
          ????????//?在最新的update對象后面插入新的update對象
          ????????update.next?=?first;
          ??????}
          ??????last.next?=?update;
          ????}
          ????//?將表頭保持在最新的update對象上
          ????queue.last?=?update;
          ???//?進行調度工作
          ????scheduleWork();
          }

          也就是我們每次執(zhí)行dispatchAction方法,比如setAge或setName。就會創(chuàng)建一個保存著此次更新信息的update對象,添加到更新鏈表queue上。然后每個Hooks節(jié)點就會有自己的一個queque。比如假設我們執(zhí)行了下面幾個語句:

          setAge(19);
          setAge(20);
          setAge(21);

          那么我們的Hooks鏈表就會變成這樣:

          在Hooks節(jié)點上面,會如上圖那樣,通過鏈表來存放所有的歷史更新操作。以便在update階段可以通過這些更新獲取到最新的值返回給我們。這就是在第一次調用useState或useReducer之后,每次更新都能返回最新值的原因。再來看看mountReducer,你會發(fā)現和mountState幾乎一摸一樣,只是狀態(tài)的初始化邏輯有那么一點區(qū)別。畢竟useState其實就是閹割版的useReducer。這里就不詳細介紹mountReducer了。

          //?react-reconciler/src/ReactFiberHooks.js
          function?mountReducer(reducer,?initialArg,?init,)?{
          ??//?獲取當前的Hook節(jié)點,同時將當前Hook添加到Hook鏈表中
          ??const?hook?=?mountWorkInProgressHook();
          ??let?initialState;
          ??//?初始化
          ??if?(init?!==?undefined)?{
          ????initialState?=?init(initialArg);
          ??}?else?{
          ????initialState?=?initialArg?;
          ??}
          ??hook.memoizedState?=?hook.baseState?=?initialState;
          ??//?存放更新對象的鏈表
          ??const?queue?=?(hook.queue?=?{
          ????last:?null,
          ????dispatch:?null,
          ????lastRenderedReducer:?reducer,
          ????lastRenderedState:?(initialState:?any),
          ??});
          ??//?返回一個dispatch方法用來修改狀態(tài),并將此次更新添加update鏈表中
          ??const?dispatch?=?(queue.dispatch?=?(dispatchAction.bind(
          ????null,
          ????currentlyRenderingFiber,
          ????queue,
          ??)));
          ?//?返回狀態(tài)和修改狀態(tài)的方法
          ??return?[hook.memoizedState,?dispatch];
          }

          然后我們來看看update階段,也就是看一下我們的useState或useReducer是如何利用現有的信息,去給我們返回最新的最正確的值的。先來看一下useState在update階段的代碼也就是updateState:

          //?react-reconciler/src/ReactFiberHooks.js
          function?updateState(initialState)?{
          ??return?updateReducer(basicStateReducer,?initialState);
          }

          可以看到,updateState底層調用的其實就會死updateReducer,因為我們調用useState的時候,并不會傳入reducer,所以這里會默認傳遞一個basicStateReducer進去。我們先看看這個basicStateReducer:

          //?react-reconciler/src/ReactFiberHooks.js
          function?basicStateReducer(state,?action){
          ??return?typeof?action?===?'function'???action(state)?:?action;
          }?

          在使用useState(action)的時候,action通常會是一個值,而不是一個方法。所以baseStateReducer要做的其實就是將這個action返回。來繼續(xù)看一下updateReducer的邏輯:

          //?react-reconciler/src/ReactFiberHooks.js
          //?去掉與fiber有關的邏輯

          function?updateReducer(reducer,initialArg,init)?{
          ??const?hook?=?updateWorkInProgressHook();
          ??const?queue?=?hook.queue;

          ??//?拿到更新列表的表頭
          ??const?last?=?queue.last;

          ??//?獲取最早的那個update對象
          ??first?=?last?!==?null???last.next?:?null;

          ??if?(first?!==?null)?{
          ????let?newState;
          ????let?update?=?first;
          ????do?{
          ??????//?執(zhí)行每一次更新,去更新狀態(tài)
          ??????const?action?=?update.action;
          ??????newState?=?reducer(newState,?action);
          ??????update?=?update.next;
          ????}?while?(update?!==?null?&&?update?!==?first);

          ????hook.memoizedState?=?newState;
          ??}
          ??const?dispatch?=?queue.dispatch;
          ??//?返回最新的狀態(tài)和修改狀態(tài)的方法
          ??return?[hook.memoizedState,?dispatch];
          }

          在update階段,也就是我們組件第二次第三次。。執(zhí)行到useState或useReducer的時候,會遍歷update對象循環(huán)鏈表,執(zhí)行每一次更新去計算出最新的狀態(tài)來返回,以保證我們每次刷新組件都能拿到當前最新的狀態(tài)。useState的reducer是baseStateReducer,因為傳入的update.action為值,所以會直接返回update.action,而useReducer 的reducer是用戶定義的reducer,所以會根據傳入的action和每次循環(huán)得到的newState逐步計算出最新的狀態(tài)。

          useState/useReducer 小總結

          看到這里我們在回頭看看最初的一些疑問:

          1. React 如何管理區(qū)分Hooks?
          1. useState和useReducer如何在每次渲染時,返回最新的值?
          1. 為什么不能在條件語句等中使用Hooks?

          比如如圖所示,我們在mount階段調用了useState('A'), useState('B'), useState('C'),如果我們將useState('B') 放在條件語句內執(zhí)行,并且在update階段中因為不滿足條件而沒有執(zhí)行的話,那么沒法正確的重Hooks鏈表中獲取信息。React也會給我們報錯。

          Hooks鏈表放在哪?

          好的,現在我們已經了解了React 通過鏈表來管理 Hooks,同時也是通過一個循環(huán)鏈表來存放每一次的更新操作,得以在每次組件更新的時候可以計算出最新的狀態(tài)返回給我們。那么我們這個Hooks鏈表又存放在那里呢?理所當然的我們需要將它存放到一個跟當前組件相對于的地方。那么很明顯這個與組件一一對應的地方就是我們的FiberNode。如圖所示,組件構建的Hooks鏈表會掛載到FiberNode節(jié)點的memoizedState上面去。

          useEffect

          看到這,相信你已經對Hooks的源碼實現模式已經有一定的了解了,所以你嘗試去看一下Effect的實現你會一下子就看懂。首先我們先回憶一下useEffect是怎么樣工作的?

          function?PersionInfo?()?{
          ??const?[age,?setAge]?=?useState(18);
          ??useEffect(()?=>{
          ??????console.log(age)
          ??},?[age])

          ?const?[name,?setName]?=?useState('Dan');
          ?useEffect(()?=>{
          ??????console.log(name)
          ??},?[name])
          ??return?(
          ????<>
          ??????...
          ????
          ??);
          }

          PersionInfo組件第一次渲染的時候會在控制臺輸出age和name,在后面組件的每次update中,如果useEffect中的deps依賴的值發(fā)生了變化的話,也會在控制臺中輸出對應的狀態(tài),同時在unmount的時候就會執(zhí)行清除函數(如果有)。React中是怎么實現的呢?其實很簡單,在FiberNode中通過一個updateQueue來存放所有的effect,然后在每次渲染之后依次執(zhí)行所有需要執(zhí)行的effect。useEffect 也分為mountEffect和updateEffect

          mountEffect

          //?react-reconciler/src/ReactFiberHooks.js
          //?簡化去掉特殊邏輯

          function?mountEffect(?create,deps,)?{
          ??return?mountEffectImpl(
          ????create,
          ????deps,
          ??);
          }

          function?mountEffectImpl(fiberEffectTag,?hookEffectTag,?create,?deps)?{
          ??//?獲取當前Hook,并把當前Hook添加到Hook鏈表
          ??const?hook?=?mountWorkInProgressHook();
          ??const?nextDeps?=?deps?===?undefined???null?:?deps;
          ??//?將當前effect保存到Hook節(jié)點的memoizedState屬性上,
          ??//?以及添加到fiberNode的updateQueue上
          ??hook.memoizedState?=?pushEffect(hookEffectTag,?create,?undefined,?nextDeps);
          }

          function?pushEffect(tag,?create,?destroy,?deps)?{
          ??const?effect:?Effect?=?{
          ????tag,
          ????create,
          ????destroy,
          ????deps,
          ????next:?(null:?any),
          ??};
          ??//?componentUpdateQueue?會被掛載到fiberNode的updateQueue上
          ??if?(componentUpdateQueue?===?null)?{
          ????//?如果當前Queue為空,將當前effect作為第一個節(jié)點
          ????componentUpdateQueue?=?createFunctionComponentUpdateQueue();
          ???//?保持循環(huán)
          ????componentUpdateQueue.lastEffect?=?effect.next?=?effect;
          ??}?else?{
          ????//?否則,添加到當前的Queue鏈表中
          ????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;?
          }

          可以看到在mount階段,useEffect做的事情就是將自己的effect添加到了componentUpdateQueue上。這個componentUpdateQueue會在renderWithHooks方法中賦值到fiberNode的updateQueue上。

          //?react-reconciler/src/ReactFiberHooks.js
          //?簡化去掉特殊邏輯
          export?function?renderWithHooks()?{
          ???const?renderedWork?=?currentlyRenderingFiber;
          ???renderedWork.updateQueue?=?componentUpdateQueue;
          }

          也就是在mount階段我們所有的effect都以鏈表的形式被掛載到了fiberNode上。然后在組件渲染完畢之后,React就會執(zhí)行updateQueue中的所有方法。

          updateEffect

          //?react-reconciler/src/ReactFiberHooks.js
          //?簡化去掉特殊邏輯

          function?updateEffect(create,deps){
          ??return?updateEffectImpl(
          ????create,
          ????deps,
          ??);
          }

          function?updateEffectImpl(fiberEffectTag,?hookEffectTag,?create,?deps){
          ??//?獲取當前Hook節(jié)點,并把它添加到Hook鏈表
          ??const?hook?=?updateWorkInProgressHook();
          ??//?依賴?
          ??const?nextDeps?=?deps?===?undefined???null?:?deps;
          ?//?清除函數
          ??let?destroy?=?undefined;

          ??if?(currentHook?!==?null)?{
          ????//?拿到前一次渲染該Hook節(jié)點的effect
          ????const?prevEffect?=?currentHook.memoizedState;
          ????destroy?=?prevEffect.destroy;
          ????if?(nextDeps?!==?null)?{
          ??????const?prevDeps?=?prevEffect.deps;
          ??????//?對比deps依賴
          ??????if?(areHookInputsEqual(nextDeps,?prevDeps))?{
          ????????//?如果依賴沒有變化,就會打上NoHookEffect?tag,在commit階段會跳過此
          ????????//?effect的執(zhí)行
          ????????pushEffect(NoHookEffect,?create,?destroy,?nextDeps);
          ????????return;
          ??????}
          ????}
          ??}
          ??hook.memoizedState?=?pushEffect(hookEffectTag,?create,?destroy,?nextDeps);
          }

          update階段和mount階段類似,只不過這次會考慮effect 的依賴deps,如果此次更新effect的依賴沒有變化的話,就會被打上NoHookEffect標簽,最后會在commit階段跳過改effect的執(zhí)行。

          function?commitHookEffectList(unmountTag,mountTag,finishedWork)?{
          ??const?updateQueue?=?finishedWork.updateQueue;
          ??let?lastEffect?=?updateQueue?!==?null???updateQueue.lastEffect?:?null;
          ??if?(lastEffect?!==?null)?{
          ????const?firstEffect?=?lastEffect.next;
          ????let?effect?=?firstEffect;
          ????do?{
          ??????if?((effect.tag?&?unmountTag)?!==?NoHookEffect)?{
          ????????//?Unmount?階段執(zhí)行tag?!==?NoHookEffect的effect的清除函數?(如果有的話)
          ????????const?destroy?=?effect.destroy;
          ????????effect.destroy?=?undefined;
          ????????if?(destroy?!==?undefined)?{
          ??????????destroy();
          ????????}
          ??????}
          ??????if?((effect.tag?&?mountTag)?!==?NoHookEffect)?{
          ????????//?Mount?階段執(zhí)行所有tag?!==?NoHookEffect的effect.create,
          ????????//?我們的清除函數(如果有)會被返回給destroy屬性,一遍unmount執(zhí)行
          ????????const?create?=?effect.create;
          ????????effect.destroy?=?create();
          ??????}
          ??????effect?=?effect.next;
          ????}?while?(effect?!==?firstEffect);
          ??}
          }

          useEffect 小總結

          useEffect做了什么?

          到此為止,useState/useReducer/useEffect源碼也閱讀完畢了,相信有了這些基礎,剩下的Hooks的源碼閱讀不會成問題,最后放上完整圖示:

          瀏覽 39
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  人人摸人人操人人干 | 大鸡巴免费看 | 欧美一性一乱一交一视爱豆传媒 | 淫色一级 | 伊人五月丁香婷婷大香蕉 |