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>

        全網最簡單的React Hooks源碼解析!

        共 14389字,需瀏覽 29分鐘

         ·

        2022-03-08 09:28

        前言

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

        使用Hooks時的疑惑

        Hooks的面世讓我們的Function Component逐步擁有了對標Class Component的特性,比如私有狀態(tài),生命周期函數(shù)等。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),并返回一個用來更新數(shù)據(jù)的方法。在使用這兩個方法的時候也許我們會想過這樣的問題:

        ??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 由于本質就是一個函數(shù),并且每次渲染都會重新執(zhí)行。所以React必定擁有某種機制去記住每一次的更新操作,并最終得出最新的值返回。當然我們還會有其他的一些問題,比如這些狀態(tài)究竟存放在哪?為什么只能在函數(shù)頂層使用Hooks而不能在條件語句等里面使用Hooks?

        答案盡在源碼之中

        我們先來了解useState以及useReducer的源碼實現(xiàn),并從中解答我們在使用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的具體實現(xiàn),大家可以多看看這個文件。首先我們注意到,我們大部分的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的實現(xiàn),先來看mountState的實現(xiàn)。

        //?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é)點是怎么利用鏈表數(shù)據(jù)結構串聯(lián)在一起的呢?相關邏輯就在每個具體mount 階段 Hooks函數(shù)調用的 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 方法的實現(xiàn):

        //?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ā)現(xiàn)和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是如何利用現(xiàn)有的信息,去給我們返回最新的最正確的值的。先來看一下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,所以會根據(jù)傳入的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鏈表放在哪?

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

        useEffect

        看到這,相信你已經對Hooks的源碼實現(xiàn)模式已經有一定的了解了,所以你嘗試去看一下Effect的實現(xiàn)你會一下子就看懂。首先我們先回憶一下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í)行清除函數(shù)(如果有)。React中是怎么實現(xiàn)的呢?其實很簡單,在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;
        ?//?清除函數(shù)
        ??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的清除函數(shù)?(如果有的話)
        ????????const?destroy?=?effect.destroy;
        ????????effect.destroy?=?undefined;
        ????????if?(destroy?!==?undefined)?{
        ??????????destroy();
        ????????}
        ??????}
        ??????if?((effect.tag?&?mountTag)?!==?NoHookEffect)?{
        ????????//?Mount?階段執(zhí)行所有tag?!==?NoHookEffect的effect.create,
        ????????//?我們的清除函數(shù)(如果有)會被返回給destroy屬性,一遍unmount執(zhí)行
        ????????const?create?=?effect.create;
        ????????effect.destroy?=?create();
        ??????}
        ??????effect?=?effect.next;
        ????}?while?(effect?!==?firstEffect);
        ??}
        }

        useEffect 小總結

        useEffect做了什么?

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

        瀏覽 39
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

          <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            艹逼网123 | chinese勾搭少妇videos | 快连NPV安卓版 | 在线的欧美成网站 | 国产又黄又爽免费 | 99精品久久久久久久免费看蜜月 | 《丰满女人》伦理hd | 体内射精免费视频 | 脱光干网| 一夲道一区二区三区视频 |