【源码】hooks源码实现

hooks源码实现

相关文章

环境

  • react:18.2.0

hooks源码实现


一、入口查找

看package.json

可以看到我们的 hook 都是从 ReactClient 文件中导入的

从 ReactHooks 导入

二、hooks 源码实现

useState

1
2
3
4
5
6
7
8
react/packages/react/src/ReactHooks.js

export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
  • useState 函数接受一个参数 initialState,它可以是一个初始状态值,也可以是一个返回初始状态值的函数。
  • resolveDispatcher 函数用于解析当前的 dispatcher,它是一个 React 内部的机制,用于管理和分发 Hook 相关的操作。
  • 然后,通过调用 dispatcher.useState(initialState)来获取实际的状态值和状态更新函数。这个调用会委托给特定 dispatcher 中的 useState 函数,它负责实现具体的状态管理逻辑。
  • 最后,将获取到的状态值和状态更新函数作为元组的形式返回,其中第一个元素是当前状态值,第二个元素是用于更新状态的函数。

总的来说,useState 函数用于在函数组件中声明和管理状态,并且它通过解析 dispatcher 来委托具体的状态管理逻辑,使得 React 可以支持不同的状态管理方式。

useCallback

1
2
3
4
5
6
7
export function useCallback<T>(
callback: T,
deps: Array<mixed> | void | null,
): T {
const dispatcher = resolveDispatcher();
return dispatcher.useCallback(callback, deps);
}
  • useCallback 函数接受两个参数:callback 表示要记忆的回调函数,deps 表示依赖数组。
  • resolveDispatcher 函数用于解析当前的 dispatcher,它是一个 React 内部的机制,用于管理和分发 Hook 相关的操作。
  • 然后,通过调用 dispatcher.useCallback(callback, deps)来获取记忆后的回调函数。这个调用会委托给特定 dispatcher 中的 useCallback 函数,它负责实现具体的回调函数记忆逻辑。
  • 最后,将记忆后的回调函数直接返回。

总的来说,useCallback 函数用于在函数组件中记忆回调函数,以避免在每次渲染时创建新的回调函数实例。它通过解析 dispatcher 来委托具体的回调函数记忆逻辑,使得 React 可以支持不同的记忆方式。

useMemo

1
2
3
4
5
6
7
export function useMemo<T>(
create: () => T,
deps: Array<mixed> | void | null,
): T {
const dispatcher = resolveDispatcher();
return dispatcher.useMemo(create, deps);
}
  • useMemo 函数接受两个参数:create 表示用于创建 memoized 值的函数,deps 表示依赖数组。
  • resolveDispatcher 函数用于解析当前的 dispatcher,它是一个 React 内部的机制,用于管理和分发 Hook 相关的操作。
  • 然后,通过调用 dispatcher.useMemo(create, deps)来获取 memoized 的值。这个调用会委托给特定 dispatcher 中的 useMemo 函数,它负责实现具体的 memoization 逻辑。
  • 最后,将 memoized 的值直接返回。

总的来说,useMemo 函数用于在函数组件中记忆计算值,以避免在每次渲染时重新计算。它通过解析 dispatcher 来委托具体的 memoization 逻辑,使得 React 可以支持不同的 memoization 策略。

useEffect

1
2
3
4
5
6
7
export function useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
const dispatcher = resolveDispatcher();
return dispatcher.useEffect(create, deps);
}
  • useEffect 函数接受两个参数:create 表示用于创建副作用的函数,deps 表示依赖数组。
  • resolveDispatcher 函数用于解析当前的 dispatcher,它是一个 React 内部的机制,用于管理和分发 Hook 相关的操作。
  • 然后,通过调用 dispatcher.useEffect(create, deps)来执行副作用函数。这个调用会委托给特定 dispatcher 中的 useEffect 函数,它负责实现具体的副作用逻辑。
  • 最后,useEffect 函数没有返回值,因为它主要用于执行副作用操作,而不是返回值。

总的来说,useEffect 函数用于在函数组件中处理副作用,如订阅数据、设置定时器等。它通过解析 dispatcher 来委托具体的副作用逻辑,使得 React 可以支持不同的副作用处理方式。

好吧,相信通过以上几个示例,已经发现了,hooks 都依赖了一个 dispatcher(resolveDispatcher 方法构造出来的)对象,委托它来进行具体的实现,并把它的返回值输出。

三、dispatcher(调度器)是啥

在 React 中,dispatcher 是一个内部机制,用于管理和分发 Hook 相关的操作。它是 React 的核心之一,负责处理函数组件中使用的各种 Hook,如 useState、useEffect、useContext 等。让我详细介绍一下 dispatcher 的功能和作用:

  • 解析 Dispatcher(Resolve Dispatcher):

    在 React 内部,使用 resolveDispatcher 函数来获取当前的 dispatcher。这个函数会根据当前环境和配置返回适当的 dispatcher 实例。

  • 具体的 Hook 逻辑(Specific Hook Logic):

    每个 dispatcher 都会实现针对不同 Hook 的具体逻辑。例如,useState、useEffect、useMemo 等 Hook 的具体实现都会在 dispatcher 中定义。

  • Hook 的注册和调用(Registration and Invocation of Hooks):

    在函数组件中使用 Hook 时,实际上是通过 dispatcher 来注册和调用 Hook。dispatcher 负责管理 Hook 的状态、副作用以及其他相关逻辑。

  • 状态管理(State Management):

    dispatcher 负责管理 Hook 中的状态,例如 useState Hook 中的状态值、useReducer Hook 中的状态和操作等。

  • 副作用处理(Side Effect Handling):

    dispatcher 也负责处理副作用,例如 useEffect Hook 中的副作用函数的调用、清除和更新。

  • 扩展性和自定义(Extensibility and Customization):

    开发人员可以根据需要扩展或自定义 dispatcher,以支持特定的功能或优化性能。这使得 React 可以根据不同的应用场景和需求进行灵活的定制和优化。

源码实现

1
2
3
4
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
return ((dispatcher: any): Dispatcher);
}

可以看到,resolveDispatcher 就是从 ReactCurrentDispatcher 对象中获取当前的 dispatcher 并返回。

那么ReactCurrentDispatcher又是啥

四、ReactCurrentDispatcher 又是啥

ReactCurrentDispatcher 是 React 内部的一个重要工具,用于在函数组件中获取当前的 dispatcher 对象,以便在 Hook 相关的操作中使用。它提供了一种方便的方式来访问 React 内部的 Hook 机制,并能够帮助开发者更好地理解和使用 React Hooks。

源码实现

1
2
3
const ReactCurrentDispatcher = {
current: (null: null | Dispatcher),
};

没想到,它竟然是一个这么简单的对象,那么它又是何时赋值的呢

ReactCurrentDispatcher 是在 React 渲染阶段中被赋值的。具体来说,它是在渲染函数组件时被赋值的。

在渲染函数组件之前,React 会将当前正在渲染的组件设置为该函数组件的 Fiber 节点。而在渲染函数组件过程中,React 会将 ReactCurrentDispatcher.current 设置为当前组件对应的 dispatcher,以便在函数组件内部可以通过 ReactCurrentDispatcher.current 来获取当前的 dispatcher 对象。

五、renderWithHooks

我在renderWithHooks方法中找到了具体的复制过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {

//...

if (__DEV__) {
//...
} else {
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
}

//...
}

职责

renderWithHooks 是 React 内部的一个方法,用于在函数组件的渲染过程中执行所有的 Hook 相关逻辑。它负责在渲染函数组件时调用和处理所有的 Hook,包括 useState、useEffect、useContext 等。具体来说,renderWithHooks 主要完成以下几个任务:

  • 初始化 Hook 相关数据结构:在渲染函数组件之前,renderWithHooks 会初始化 Hook 相关的数据结构,包括创建 Fiber Hooks 链表,以及初始化 Hook 相关的上下文。

  • 执行 Hook 相关逻辑:在渲染函数组件过程中,renderWithHooks 会按照 Hook 在代码中的顺序依次执行,调用相应的 Hook 函数,并处理其返回值。它会根据需要创建、更新或销毁 Hook 相关的状态,并保证每次渲染都能正确地执行 Hook 相关的逻辑。

  • 处理更新优先级:renderWithHooks 会根据 Hook 的更新优先级来判断是否需要暂停执行 Hook 相关的逻辑,并根据情况进行调度,以确保渲染过程的顺利进行。

  • 处理异常情况:renderWithHooks 会处理 Hook 相关逻辑中可能出现的异常情况,比如 Hook 的调用顺序不正确、Hook 在条件语句中的使用等,以确保渲染过程的稳定性和健壮性。

总的来说,renderWithHooks 是 React 内部的一个重要方法,负责管理和执行函数组件中所有的 Hook 相关逻辑,是 React Hooks 实现的核心之一。

那么HooksDispatcherOnMount和HooksDispatcherOnUpdate又是什么

  • HooksDispatcherOnMount 用于处理函数组件的挂载阶段,即在组件初次渲染时执行。HooksDispatcherOnUpdate 用于处理函数组件的更新阶段,即在组件发生重新渲染时执行。

  • 当组件初次渲染时,React 会调用 HooksDispatcherOnMount 来执行函数组件中的所有 Hook 相关逻辑,包括初始化状态、设置副作用、订阅数据等操作。当组件发生重新渲染时,React 会调用 HooksDispatcherOnUpdate 来执行函数组件中的所有 Hook 相关逻辑,包括更新状态、处理副作用、订阅数据等操作。

六、HooksDispatcherOnMount和HooksDispatcherOnUpdate具体实现

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const HooksDispatcherOnMount: Dispatcher = {
readContext,
use,
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useInsertionEffect: mountInsertionEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
useDebugValue: mountDebugValue,
useDeferredValue: mountDeferredValue,
useTransition: mountTransition,
useSyncExternalStore: mountSyncExternalStore,
useId: mountId,
};

const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
use,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useInsertionEffect: updateInsertionEffect,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
useDebugValue: updateDebugValue,
useDeferredValue: updateDeferredValue,
useTransition: updateTransition,
useSyncExternalStore: updateSyncExternalStore,
useId: updateId,
};

果然,他们在这里定义了一系列具体hooks的实现,包括初始化和更新的方法。挑两个看看

mountMemo 与 updateMemo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function mountMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const nextValue = nextCreate();
if (shouldDoubleInvokeUserFnsInHooksDEV) {
setIsStrictModeForDevtools(true);
nextCreate();
setIsStrictModeForDevtools(false);
}
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
  • 参数:

    • nextCreate: 一个函数,用于计算新的 memoized 值。
    • deps: 依赖数组,用于判断 memoized 值是否需要更新。
  • 获取 Hook 对象:

    • 首先,通过 mountWorkInProgressHook() 获取当前正在挂载的 Hook 对象。
  • 计算新的 memoized 值:

    • 调用 nextCreate() 函数计算新的 memoized 值 nextValue。
  • 开发者工具严格模式:

    • 如果开发者工具配置为严格模式 (shouldDoubleInvokeUserFnsInHooksDEV 为 true),则在计算新的 memoized 值前后,会调用两次 nextCreate()。
    • 这样做是为了检测 Hooks 函数是否产生了副作用。
  • 更新 Hook 状态:

    • 将新的 memoized 值 nextValue 和依赖数组 nextDeps 组成的元组 [nextValue, nextDeps] 赋值给当前 Hook 的 memoizedState 属性。
    • 这里使用了 memoizedState 属性来存储 memoized 值和依赖数组,以便后续在组件重新渲染时进行比较。
  • 返回新的 memoized 值:

    • 返回新的 memoized 值 nextValue。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function updateMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
// Assume these are defined. If they're not, areHookInputsEqual will warn.
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
const nextValue = nextCreate();
if (shouldDoubleInvokeUserFnsInHooksDEV) {
setIsStrictModeForDevtools(true);
nextCreate();
setIsStrictModeForDevtools(false);
}
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
  • 参数:

    • nextCreate: 一个函数,用于计算新的 memoized 值。
    • deps: 依赖数组,用于判断 memoized 值是否需要更新。
  • 获取 Hook 对象:

    • 首先,通过 updateWorkInProgressHook() 获取当前的 Hook 对象,该 Hook 对象包含了 memoized 值和其他相关状态。
  • 检查依赖是否发生变化:

    • 如果 deps 不为 null,则获取上一次 memoized 值的依赖数组 prevDeps。
    • 使用 areHookInputsEqual 函数比较当前的依赖数组 nextDeps 和上一次的依赖数组 prevDeps 是否相等。
    • 如果依赖数组相等,则直接返回上一次 memoized 值,表示不需要重新计算。
  • 计算新的 memoized 值:

    • 调用 nextCreate() 函数计算新的 memoized 值 nextValue。
  • 开发者工具严格模式:

    • 如果开发者工具配置为严格模式 (shouldDoubleInvokeUserFnsInHooksDEV 为 true),则在计算新的 memoized 值前后,会调用两次 nextCreate()。
    • 这样做是为了检测 Hooks 函数是否产生了副作用。
  • 更新 Hook 状态:

    • 将新的 memoized 值 nextValue 和依赖数组 nextDeps 组成的元组 [nextValue, nextDeps] 赋值给当前 Hook 的 memoizedState 属性。
    • 返回新的 memoized 值 nextValue。

mountEffect 与 updateEffect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function mountEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
if (
__DEV__ &&
(currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode &&
(currentlyRenderingFiber.mode & NoStrictPassiveEffectsMode) === NoMode
) {
mountEffectImpl(
MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
HookPassive,
create,
deps,
);
} else {
mountEffectImpl(
PassiveEffect | PassiveStaticEffect,
HookPassive,
create,
deps,
);
}
}
  • 参数:

    • create: 一个函数,用于创建副作用的函数,通常是一个 effect 函数,返回一个清除副作用的函数。
    • deps: 依赖数组,用于判断副作用是否需要重新创建或清除。
  • 严格模式检查:

    • 如果当前渲染的 Fiber 节点处于严格模式 (StrictEffectsMode),并且不处于无严格模式下的被动副作用模式 (NoStrictPassiveEffectsMode),则将标记设置- 为 MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,表示副作用是被动的(即不会在每次渲染时触发),并且可能包含开发环境下的被动副作用。
    • 否则,将标记设置为 PassiveEffect | PassiveStaticEffect,表示普通的被动副作用。
  • 调用 mountEffectImpl:

    • 调用 mountEffectImpl 函数来执行副作用的挂载过程。
    • 传入的标记表示副作用的类型,这里根据严格模式的不同情况选择不同的标记。
    • 传入 HookPassive 表示这是一个被动副作用。
  • 副作用挂载:

    • mountEffectImpl 函数会根据传入的标记来执行副作用的挂载过程,包括创建副作用函数、注册副作用等操作。
    • 如果副作用具有依赖数组 (deps),则在副作用的挂载过程中会检查依赖数组是否发生变化,如果发生变化则重新创建副作用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function updateEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}

function useEffectEventImpl<Args, Return, F: (...Array<Args>) => Return>(
payload: EventFunctionPayload<Args, Return, F>,
) {
currentlyRenderingFiber.flags |= UpdateEffect;
let componentUpdateQueue: null | FunctionComponentUpdateQueue =
(currentlyRenderingFiber.updateQueue: any);
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
componentUpdateQueue.events = [payload];
} else {
const events = componentUpdateQueue.events;
if (events === null) {
componentUpdateQueue.events = [payload];
} else {
events.push(payload);
}
}
}
  • updateEffect 函数:

    • updateEffect 函数接受两个参数:create 和 deps。
    • 在函数组件的更新阶段,当调用 useEffect Hook 时,React 会调用 updateEffect 函数来执行副作用的更新逻辑。
    • updateEffect 函数会调用内部的 updateEffectImpl 函数来执行副作用的更新过程。
  • useEffectEventImpl 函数:

    • useEffectEventImpl 函数接受一个参数 payload,该参数表示一个事件函数的有效载荷。
    • 在函数组件的更新阶段,当存在副作用事件需要处理时,React 会调用 useEffectEventImpl 函数来处理副作用事件。
    • useEffectEventImpl 函数会将副作用事件添加到组件更新队列中,以便在组件更新时执行相应的副作用函数。

七、总结

在 React 中,Hooks 是一种用于在函数式组件中添加状态和副作用逻辑的特殊函数。Hooks 是由 Dispatcher 进行管理和执行的。Dispatcher 是 React 内部的一个机制,用于管理 Hooks 的创建、更新和执行。Hooks 和 Dispatcher 之间的关系可以总结如下:

Hooks:

  • Hooks 是一组特殊的函数,如 useState、useEffect、useReducer 等,它们允许函数式组件拥有状态和副作用。
  • 每个 Hook 函数都会在组件的渲染过程中被调用,并且与特定的组件实例相关联。

Dispatcher:

  • Dispatcher 是 React 内部的机制,负责管理 Hooks 的执行。
  • Dispatcher 负责跟踪当前正在渲染的组件实例,并负责执行组件中使用的每个 Hook 函数。
  • 在组件的渲染过程中,Dispatcher 会按顺序调用每个 Hook 函数,并确保它们按照正确的顺序执行。
  • Dispatcher 还负责处理 Hooks 的更新逻辑,例如在组件更新时重新执行具有更新的 Effect Hook。

关系:

  • Hooks 是开发者在函数式组件中使用的 API,而 Dispatcher 是 React 内部用于管理和执行 Hooks 的机制。
  • 开发者使用 Hooks 来定义组件的状态和副作用逻辑,而 Dispatcher 负责在组件渲染过程中执行这些逻辑,并确保它们按照正确的顺序执行。
  • 在 React 应用程序中,开发者通过使用 Hooks 来定义组件的行为,而 Dispatcher 负责在运行时实现这些行为。

喜欢这篇文章?打赏一下支持一下作者吧!
【源码】hooks源码实现
https://www.cccccl.com/20240309/源码/react/hooks源码实现/
作者
Jeffrey
发布于
2024年3月9日
许可协议