本系列文章總共三篇:javascript
React hooks api 是在 react 這個庫裏面定義的,咱們以 useState 爲例:java
export function useState<S>(initialState: (() => S) | S) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
複製代碼
咱們能夠發現 hooks 的定義很是簡單,只是獲取了 dispatch 而後調用 dispatcher 對應的 useState 屬性,其它 hooks 也是相似,好比 useEffect 是調用 dispatcher 的 useEffect 屬性。react
接着咱們就須要看看 dispatcher 究竟是什麼,經過查看 resolveDispatcher 咱們發現 dispatcher 指向的是 ReactCurrentDispatcher.current。git
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
invariant(
dispatcher !== null,
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
' one of the following reasons:\n' +
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
'2. You might be breaking the Rules of Hooks\n' +
'3. You might have more than one copy of React in the same app\n' +
'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
);
return dispatcher;
}
複製代碼
經過全局搜索咱們發現 **ReactCurrentDispatcher.current **在 ReactFiberHooks.js 這個文件中被賦值,接下來咱們就來看看這個文件。github
通過搜索咱們發現 **ReactCurrentDispatcher.current **在 ReactFiberHooks.js 文件中被頻繁賦值,其中最主要被賦值的地方就在 renderWithHooks 方法中,通過搜索我發現 renderWithHooks 在 ReactFiberBeginWork.js 這個文件中被屢次調用,若是你以前看過上一篇文檔或是對 react 的更新流程的源碼比較熟悉的話,你應該知道 ReactFiberBeginWork.js 文件對應着 beginWork 這個方法,在這個方法中會找出要更新的 fiber 對象並執行對應的更新方法。
通過搜索我找到了和 function component 相關的幾個方法:updateFunctionComponent 和 mountIndeterminateComponent,這兩個都是更新 function component,區別是第一次渲染的時候會調用 mountIndeterminateComponent,由於第一次還沒法肯定是 function component 仍是 class component。api
mountIndeterminateComponent:
app
updateFunctionComponent:
ide
接下來咱們就來看看 renderWithHooks 到底作了什麼。函數
經過上面的流程圖,咱們發現 renderWithHooks 作了以下幾件事:post
HooksDispatcherOnMount 對象中定義了各個 hooks api 在初次渲染中的實現
HooksDispatcherOnUpdate 對象中定義了各個 hooks api 在再次渲染中的實現
通過前面的講述此時你應該知道 useState 最終調用的是 ReactCurrentDispatcher.current.useState 而 ReactCurrentDispatcher.current 又是在 renderWithHooks 中被賦值爲 HooksDispatcherOnMount 或 HooksDispatcherOnUpdate,那咱們先來看一下 HooksDispatcherOnMount 中的實現。
[hook.memoizedState, dispatch]
updateState 內部調用了 updateReducer,updateRecucer 內部作了如下事情:
queue.lastRenderedReducer
爲 basicStateReducerdispatchAction 就是 useState 返回的第二個參數
通過前面的講述此時你應該知道 useEffect 和 useState 同樣,最終調用的是 ReactCurrentDispatcher.current.useEffect 而 ReactCurrentDispatcher.current 又是在 renderWithHooks 中被賦值爲 HooksDispatcherOnMount 或 HooksDispatcherOnUpdate,那咱們先來看一下 HooksDispatcherOnMount 中的實現。
HooksDispatcherOnMount 中 useEffect 指向的是 mountEffect,它又調用了 mountEffectImpl
function mountEffect( create: () => (() => void) | void, deps: Array<mixed> | void | null, ): void {
return mountEffectImpl(
UpdateEffect | PassiveEffect,
UnmountPassive | MountPassive,
create,
deps,
);
}
複製代碼
mountEffectImpl 作了如下事情:
在更新階段會將 dispatcher 指向 HooksDispatcherOnUpdate,在 HooksDispatcherOnUpdate 中 useEffect 指向的是 updateEffect,它又調用了 updateEffectImpl。
function updateEffect( create: () => (() => void) | void, deps: Array<mixed> | void | null, ): void {
return updateEffectImpl(
UpdateEffect | PassiveEffect,
UnmountPassive | MountPassive,
create,
deps,
);
}
複製代碼
updateEffectImpl 作了如下事情:
const effect: Effect = {
tag, // hookEffectTag
create, // useEffect 接收的第一個參數
destroy, // 在 mountEffect 中是 undefined
deps, // useEffect 接收的第二個參數
// Circular
next: (null: any), // 指向下一個 effect
};
複製代碼
最終生成的 updateQueue 會在 commit 階段的 commitLayoutEffects 中執行
詳情能夠看上一篇
還記得上面 mountEffectImpl 方法會將 UpdateEffect | PassiveEffect 設置到 fiber.effectTag 上,對於有 UpdateEffect 的 fiber 對象在 commitLayoutEffects 中會執行 commitLayoutEffectOnFiber 方法,它對應的就是 commitLifeCycles 方法,在該方法中對於 FunctionComponent 會執行 commitHookEffectList方法,傳入 UnmountLayout, MountLayout, finishedWork
在該方法中會對傳入的 finishedWork.updateQueue 上面的 effect 對象執行 unmount 和 mount,也就是調用 effect 對象上的 destroy 方法和 create 方法,對應於 useEffect 返回的方法和傳入的方法,第一次渲染設置的 destroy 爲 undefined 因此第一次渲染 destroy 不會執行
useRef 和其它 hooks 同樣最終調用的是 ReactCurrentDispatcher.current.useRef 而 ReactCurrentDispatcher.current 又是在 renderWithHooks 中被賦值爲 HooksDispatcherOnMount 或 HooksDispatcherOnUpdate,那咱們先來看一下 HooksDispatcherOnMount 中的實現。
在 HooksDispatcherOnMount 中 useRef 指向的是 mountRef 方法,咱們來看一下它作了什麼:
const ref = { current: initialValue };
初始值就是傳入 useRef 的第一個參數hook.memoizedState = ref;
在 HooksDispatcherOnUpdate 中 useRef 指向的是 updateRef 方法,咱們來看一下它作了什麼:
通過上面的幾個 hook api 的實現咱們發現每一個 hook api 都須要先建立一個 hook 對象,而建立 hook 對象針對初次渲染和再次渲染這兩個階段調用的方法有所不一樣,咱們先來看初次渲染。
初次渲染調用的是 mountWorkInProgressHook 方法,咱們來看一下它作了什麼:
接下來咱們看看再次渲染時調用的 updateWorkInProgressHook 方法:
咱們來看看 hook 對象究竟是個什麼東西
const hook: Hook = {
memoizedState: null,
baseState: null,
queue: null,
baseUpdate: null,
next: null,
};
複製代碼
存儲 hook 對象的數據,useState 對應的就是 state,useEffect 對應的就是 effect 對象,useRef 對應的就是 ref 對象
和 useState 相關,在初次渲染時等於傳入的初始 state,後續是每次計算出的新的 state
相似於 fiber 對象的 updateQueue,每次調用 useState 返回的 setSomeState 方法就會建立一個 update 對象放到 queue 中,而後在 render 階段再遍歷 queue 計算出新的 state
const queue = (hook.queue = {
last: null, // 指向最後一個 update,它的 next 指向第一個 update,這是一個循環鏈表
dispatch: null, // dispatch 方法,用於計算出新的 state
lastRenderedReducer: reducer, // 最後一個 update 的 reducer
lastRenderedState: (initialState: any), // 指向最後一個 update 產生的 state
});
複製代碼
包含帶註釋的源碼、demos和流程圖
github.com/kwzm/learn-…