React 源碼解析 - Hooks 系列
前言
這篇要來研究下每次寫 react 都在使用的 Hooks。說到 hooks,大家一定都用過,我自己也常用,但是一直都不知道 hook 背後到底是怎麼運作的,出現 bug 時就是百度解決辦法,半猜半試解決,其實都有點不明所以。所以,今天就要來把 react hooks 慢慢深入解析一下。
正文
調用 React Hook 到底背後發生了什麼?
前情提要下,這篇分析 hooks 所用的 React 源碼是 v16.13.1 的,因為最新版本的源碼關於 hooks 這塊個人能力不夠,不是看的很清楚,所以還是先以穩定版本來看,不過具體實現也都沒有大問題的。
初探 Hooks 源碼
當我們在調用 useXXX()
時到底背後發生了什麼?雖然看源碼真的有點累、挺難頂的,但是要想真正理解原理,還是看源碼最實在。
平時我們在項目中引入 hooks 時一般都是這樣寫的:
import React, {
useState } from 'react';
後面的 from 'react'
其實就提示了我們 useState
這個 hook 源碼藏在 /react
這個目錄下,所以順藤摸瓜,最後就找到了導出所有 hooks 的地方:react/packages/react/src/React.js
,跟 hooks 相關的源碼如下:
...
import {
useCallback,
useContext,
useEffect,
useImperativeHandle,
useDebugValue,
useLayoutEffect,
useMemo,
useReducer,
useRef,
useState,
useResponder,
useTransition,
useDeferredValue,
} from './ReactHooks';
...
...
export {
Children,
createRef,
Component,
PureComponent,
...
useCallback,
useContext,
useEffect,
useImperativeHandle,
useDebugValue,
useLayoutEffect,
useMemo,
useReducer,
useRef,
useState,
...
};
這個文件中,我們看到其實所有 hooks 都來自 /ReactHooks.js
,所以當然就再去看看啦~
Dispatcher
/ReactHooks.js
源碼如下:
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {
Dispatcher} from 'react-reconciler/src/ReactInternalTypes';
import type {
MutableSource,
MutableSourceGetSnapshotFn,
MutableSourceSubscribeFn,
ReactContext,
} from 'shared/ReactTypes';
import type {
OpaqueIDType} from 'react-reconciler/src/ReactFiberHostConfig';
import ReactCurrentDispatcher from './ReactCurrentDispatcher';
type BasicStateAction<S> = (S => S) | S;
type Dispatch<A> = A => void;
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
if (__DEV__) {
if (dispatcher === null) {
console.error(
'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://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.',
);
}
}
// Will result in a null access error if accessed outside render phase. We
// intentionally don't throw our own error because this is in a hot path.
// Also helps ensure this is inlined.
return ((dispatcher: any): Dispatcher);
}
export function getCacheForType<T>(resourceType: () => T): T {
const dispatcher = resolveDispatcher();
// $FlowFixMe This is unstable, thus optional
return dispatcher.getCacheForType(resourceType);
}
export function useContext<T>(Context: ReactContext<T>): T {
const dispatcher = resolveDispatcher();
if (__DEV__) {
// TODO: add a more generic warning for invalid values.
if ((Context: any)._context !== undefined) {
const realContext = (Context: any)._context;
// Don't deduplicate because this legitimately causes bugs
// and nobody should be using this in existing code.
if (realContext.Consumer === Context) {
console.error(
'Calling useContext(Context.Consumer) is not supported, may cause bugs, and will be ' +
'removed in a future major release. Did you mean to call useContext(Context) instead?',
);
} else if (realContext.Provider === Context) {
console.error(
'Calling useContext(Context.Provider) is not supported. ' +
'Did you mean to call useContext(Context) instead?',
);
}
}
}
return dispatcher.useContext(Context);
}
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
export function useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const dispatcher = resolveDispatcher();
return dispatcher.useReducer(reducer, initialArg, init);
}
export function useRef<T>(initialValue: T): {
|current: T|} {
const dispatcher = resolveDispatcher();
return dispatcher.useRef(initialValue);
}
export function useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
const dispatcher = resolveDispatcher();
return dispatcher.useEffect(create, deps);
}
export function useLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
const dispatcher = resolveDispatcher();
return dispatcher.useLayoutEffect(create, deps);
}
export function useCallback<T>(
callback: T,
deps: Array<mixed> | void | null,
): T {
const dispatcher = resolveDispatcher();
return dispatcher.useCallback(callback, deps);
}
export function useMemo<T>(
create: () => T,
deps: Array<mixed> | void | null,
): T {
const dispatcher = resolveDispatcher();
return dispatcher.useMemo(create, deps);
}
export function useImperativeHandle<T>(
ref: {
|current: T | null|} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
)