React 原理系列 —— Hook 是这样工作的
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
Part 1 函数式组件和 Hook
通常情况下,我们在函数式组件中这样调用 hook:
function Example() { const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>add</button> </div> ); }
函数式组件本身是个纯函数,没有任何状态,它通过调用 useState 获取一个状态和改变状态的方法。但这个 useState 是 React 导出的“全局”函数,和当前函数式组件没有任何显式关联。所以必定有某种力量将一个函数式组件实例和它用到的 state 绑定起来。
Fiber 上的 Hook
这个力量就是 Fiber,在 Fiber 节点中有两个相关属性:
- type:指向 Component,可能是 Function Component,也可能是 Class Component 等其他组件。对 Function Component 来说,就是一个具体的 render 函数,比如上面的 Example 函数。
- memoizedState:指向自身状态,在 Class Fiber 下是构造函数声明的 state,在 Function Fiber 下则是一个 Hook 池。Hook 池中维护着组件调用 useXXX 产生的所有 Hook,Hook 中又分别记着各自的状态。这样就实现了 Hook 和 Fiber 的绑定。

Hook 池和 Fiber 绑定的意义在于,当某个 Function Component 的 Fiber 开始 render,它能根据状态池定位到上一次 render 的 Hook 状态,为本次 render 执行的所有 useXXX 提供行为依据,“一次性”函数就有了“延续”的状态。
找到“上一次”
任意一个 Function Component Fiber 更新时都会走到 renderWithHooks 方法:
function updateFunctionComponent( current, workInProgress, Component, nextProps, renderExpirationTime ) { nextChildren = renderWithHooks(current,workInProgress,Component, nextProps,context,renderExpirationTime); }
这个方法在 ReactFiberHooks 模块中,模块里有全局的 nextCurrentHook 指针,表明当前指向的 Hook。renderWithHooks 会首先切换 nextCurrentHook 到当前 Fiber 的 Hook 池,再执行 render 函数,然后 render 函数中调用的所有“全局”useXXX 都从这个指针获取“上一次”。

function renderWithHooks(current,workInProgress,Component,props,refOrContext,nextRenderExpirationTime) { // 切换 nextCurrentHook nextCurrentHook = current !== null ? current.memoizedState : null; // 执行 render 方法 let children = Component(props, refOrContext); return children; }
弄清楚 Hook 和 Fiber 的绑定和切换,接下来我们进到一个 Fiber 节点内部,看看 Hook 池的维护机制。
Part 2 Hook 池的维护机制
Hook 机制的所有实现,都在前面提到的 ReactFiberHooks 模块中。
Hook 的数据结构
Hook 是一个对象,render 中调用 useXXX 方法,就会创建一个 Hook 对象。
type Hook = { memoizedState: any; baseState: any; baseUpdate: Update<any, any> | null; queue: UpdateQueue<any, any> | null; next: Hook | null; }
如果你调用的方法不一样,Hook 对象里面的字段搭载的信息也不一样。比如 useState、useReducer 这样的 State Hook,和 useEffect 这样的 Effect Hook,就会在 memoizedState 上存不同的东西。
当组件调用多次 useXXX,就会创建多个 Hook。同一 Component 的多个 Hook 之间用链表连接起来,构成 Hook 池,Fiber 的 memoizedState 就指向池中第一个 Hook:

除了 nextCurrentHook,ReactFiberHooks 提供了一些其他指针来做遍历:
let currentHook: Hook | null = null; let nextCurrentHook: Hook | null = null; let firstWorkInProgressHook: Hook | null = null; let workInProgressHook: Hook | null = null; let nextWorkInProgressHook: Hook | null = null;
前两个指针用于遍历已有的 Hook 池,后三个指针用来构建一个新 Hook 池。
方法换档
虽然看起来 Function Component 每次 render 都调用的同一个 useXXX,但实际上 mount 和 update 调用对 Hook 池是几乎完全不同的操作。
因此 ReactFiberHooks 提供了一个换档机制:声明两套 HooksDispatcher,上面绑定了 mount、update 阶段不同的 Hook 实现。当一个 Function Component 准备 render 时,判断它是 mount 还是 update,切换不同的 HooksDispatcher。

具体的换挡逻辑仍在 renderWithHooks 中,而对 mount 还是 update 的判断,则依赖当前 Fiber 是否有 Hook 池:
nextCurrentHook = current !== null ? current.memoizedState : null; ReactCurrentDispatcher.current = nextCurrentHook === null ? HooksDispatcherOnMount : HooksDispatcherOnUpdate;
这里其实不合理,如果组件没用到 Hook,即使在 update 阶段,也不会有 Hook 池。但也无所谓,反正发动机没转,换哪个档都不会动。既然能换挡,我们也就能看到不同阶段下,Hook 池的构建和读取方式。
Hook 池的构建
Fiber mount 阶段,Fiber 上没有 Hook 池,从头构建:

- 构造新 Hook。
- 接入链表。如果 Fiber 上没有 Hook,说明当前 Hook 是整个函数式组件的第一个 Hook,放 firstWorkInProgressHook 最后接造 Fiber 上;如果有 Hook,直接 next 接下去。
- 更新指针。
这部分逻辑抽象在每个 mountXXX 都要调的 mountWorkInProgressHook 中。
function mountWorkInProgressHook(): Hook { // 1. 构造新 Hook const hook: Hook = { memoizedState: null, baseState: null, queue: null, baseUpdate: null, next: null, }; // 2. 接入链表;3. 更新指针 if (workInProgressHook === null) { firstWorkInProgressHook = workInProgressHook = hook; } else { workInProgressHook = workInProgressHook.next = hook; } return workInProgressHook; }
mountWorkInProgressHook 返回创建的 Hook,随后不同的 mountXXX 会再对 Hook 做各自的处理,后面分开说。
等整个 Hook 池构建完毕,在 renderWithHooks 中挂到 Fiber 的 memoizedState 上:
// renderWithHooks 方法 renderedWork.memoizedState = firstWorkInProgressHook;
Hook 池的更新
Fiber 开始 update,通过 renderWithHooks 读取 Fiber.memoizedState 上的第一个 Hook,并给到 nextCurrentHook 指针。
nextCurrentHook = current !== null ? current.memoizedState : null;

然后每个逐个进入 updateXXX。我们以第一个 Hook 操作为例,会调通用的 updateWorkInProgressHook 方法执行以下操作:

- 克隆当前 Hook。为什么不直接复用?这样可以保证 Fiber 上的 Hook “原件”完整,某些情况下(比如 useEffect 要对比新旧 Hook 的依赖),在构建当前 Hook 的同时,仍需要上一次 Hook 的信息。
- 更新指针。沿着 next 把“当前 Hook”指针指向链表的下一次节点,同时 workInProgressHook 也通过 next 构建下去。这就是网红问题 “为什么 Hook 不能用在 if / for 语句里?” 的答案,一旦中间某次 useXXX 没 next,会导致后续所有 Hook 取错。
附部分代码:
function updateWorkInProgressHook() { currentHook = nextCurrentHook; // 1. 克隆 Hook const newHook: Hook = { memoizedState: currentHook.memoizedState, baseState: currentHook.baseState, queue: currentHook.queue, baseUpdate: currentHook.baseUpdate, next: null, }; // 2. 更新指针 if (workInProgressHook === null) { workInProgressHook = firstWorkInProgressHook = newHook; } else { workInProgressHook = workInProgressHook.next = newHook; } nextCurrentHook = currentHook.next; return workInProgressHook; }
等 Hook 池更新完毕,renderWithHooks 同样会把 Fiber 的 memoizedState 切换到 firstWorkInProgressHook。

小结
这节介绍了 Hook 的基本机制:
- 函数内的 Hook 调用创建一个 Hook 对象,上面保存着状态数据。
- Hook 维护在一个 Hook 池中,并挂到 Fiber 节点上,Hook 池是一个单向链表。
- Fiber 在 mount 和 update 阶段通过“换挡”切换 dispatcher,调用不同的 useXXX 实现。
- mount 阶段要构建 Hook 池;update 阶段则逐个克隆 Hook,构建新的 Hook 池。
Part 3 State Hook:提供状态
State Hook 来自 useState、useReducer,用来给函数式组件提供状态,它的实现围绕状态及其更新。
const [ state, dispatch ] = useState(initial);
State Hook 结构
在 State Hook 中,memoizedState 保存着 Hook 的最新状态,baseState 则是初始状态(useState 传入)。queue 保存着这个 hook 的更新队列,数据结构为单向循环链表:

State Hook 构建:mount 阶段
State Hook 经过通用的构造阶段,执行自己的逻辑,我们补充到下图中:

- 状态初始赋值。也就是我们调用 useState 的入参。
- 构造更新队列,绑定 dispatch。更新队列会在需要更新的时候被“清洗”来计算最新 memoizedState,而 dispatch 是修改队列的唯一入口。
- 返回 state 和 dispatch。这样我们第一次调用 useState 完成,获得 Hook 的初始状态值和更新状态的方法。
附代码:
function mountState() { const hook = mountWorkInProgressHook(); // 4. 状态初始赋值 hook.memoizedState = hook.baseState = initialState; // 5. 构造更新队列 const queue = (hook.queue = { last, dispatch, lastRenderedReducer, lastRenderedState }); const dispatch = (queue.dispatch = (dispatchAction.bind( null, ((currentlyRenderingFiber: any): Fiber), queue, ))); // 6. 返回 state 和 dispatch return [hook.memoizedState, dispatch]; }
dispatchAction 在 mount 阶段绑定到 Hook 上并返回,后续的更新直接来 Hook 上调就好。
State Hook 读取:update 阶段
Fiber update 阶段,useState 从 Hook 池中克隆出 Hook,获取历史状态,计算新状态。以第一个 Hook 操作为例,补齐后续几步:

- 计算最新 state。这是个清理 Update Queue 的动作,可选,如果两次 render 之间没有对这个 Hook 节点的 set 操作,就不会有 Update,可以直接返回现有 state。
- 返回最新 state 和 dispatch。
附部分代码:
function updateReducer() { const hook = updateWorkInProgressHook(); const queue = hook.queue; // 3. 合并 queue,计算最新 memoizedState let newState = hook.memoizedState; let update = firstRenderPhaseUpdate; do { const action = update.action; newState = reducer(newState, action); update = update.next; } while (update !== null); hook.memoizedState = newState; const dispatch: Dispatch<A> = (queue.dispatch: any); // 4. return [hook.memoizedState, dispatch]; }
State Hook 更新:dispatchAction
dispatchAction 是第一次调 useState 时返回出去的,作为 setXXX 调用。那很显然,setXXX 把传入的更新加入 Hook 更新队列,并触发一次更新。这里借用之前那篇的图:( React Fiber 架构 —— “更新”到底是个啥 - 知乎 )

很简单的插入链表操作,完成后通过 scheduleWork 发起更新。
代码:
function dispatchAction<S, A>(fiber: Fiber, queue: UpdateQueue<S, A>, action: A) { // 1. 构造更新 const update: Update<S, A> = { expirationTime, action, eagerReducer: null, eagerState: null, next: null, }; // 2. 插入队列 const last = queue.last; if (last === null) { update.next = update; } else { const first = last.next; if (first !== null) { update.next = first; } last.next = update; } queue.last = update; // 3. 发起调度 scheduleWork(fiber, expirationTime); }
小结
这节介绍了 Stete Hook 的实现:
- State Hook 来自 useState、useReducer,用来提供状态及其更新。
- State Hook 通过 memoizedState 保存状态,通过 queue 维护更新队列的数据和方法(dispatch)。
- State Hook 的更新队列是个单向循环链表。
- 更新阶段的 State Hook 会“清洗”更新队列,计算并返回最新 memoizedState。
- 我们调用 useState 返回的 dispatch,就是创建并在更新队列中插入新更新,并发起整体调度。
Part 4 Effect Hook:依赖监听和清理
Effect Hook 来自 useEffect、useLayoutEffect,为函数式组件提供依赖监听。
useEffect(() => { // create Effect return () => { // destroy Effect }; }, [deps]);
Effect Hook 结构
在 Effect Hook 上,只有 memoizedState 被用到,用来存储一个 Effect 对象。这个对象会被插入 Fiber 的更新队列,告诉 Fiber 更新后要做哪些动作。
type Effect = { tag: HookEffectTag, create: () => (() => void) | void, destroy: (() => void) | void, deps: Array<mixed> | null, next: Effect, };
- create:就是我们传入的第一个回调函数,在依赖变化的时候执行
- destroy:用来记录 create 执行后的返回函数。某些阶段来自于上一次同一个 Effect Hook 的 create 执行结果,某些阶段来自于自身。
- deps:就是我们传入的依赖数组,用来进行依赖对比。
- tag:决定 Effect 在 Fiber 提交时如何被处理。这个很关键,它的值来自于 useEffect 的执行时机和依赖变化情况。
Effect Hook 构建和更新
第一次渲染,走到 mountEffect,通过通用 mountWorkInProgressHook 构造并插入一个 Hook 返回。然后对这个 Hook 做一些操作,我们关注 4、5、6 步:

- 构造 Effect(副作用对象)。记录了副作用的依赖数组、回调函数、清理函数、处理方式。
- Effect 加入 Fiber 节点的 updateQueue。updateQueue 是个链表,链表元素就是我们构造的 Effect,这个链表会在 Fiber 更新完毕后逐个根据 Effect 对象提供的信息处理和清空。
- Effect 同时也会挂到当前 Hook 上。
构建代码:
function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void { const hook = mountWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; hook.memoizedState = pushEffect(hookEffectTag, create, undefined, nextDeps); } function pushEffect(tag, create, destroy, deps) { const effect: Effect = {...}; 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; }
更新执行函数式组件时,每个 useEffect 又会被分别执行一次,同样要分别克隆 Hook,然后构造 Effect 挂到 updateQueue 和 Hook 上。

- 对比旧 Hook,确定 Effect 对象的属性。这是因为 Effect 的内容要依赖前面的 Effect,比如销毁函数(destroy)就是由上一次执行创建函数(create)返回的。下一节我们展开看。
- 构造 Effect
- Effect 入队
- Effect 挂到当前 Hook 上
代码:
function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void { const hook = updateWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; let destroy = undefined; if (currentHook !== null) { // 4. 对比旧 Hook,确定 Effect 对象的属性 // ...暂时省略,后面展开说 } hook.memoizedState = pushEffect(hookEffectTag, create, destroy, nextDeps); }
至此,无论 Fiber 初始化还是更新,我们所有 useEffect 产生的 Effect,都可以从 Hook 池和 Update Queue 访问到。其中 Hook 池方便我们管理所有 Hook(包括之前 State Hook)的数据,Effect 就是 Effect Hook 的数据;Update Queue 则为了方便 Fiber 直接拿到 Effect 执行副作用。
Effect 属性的取值和流转
接下来我们看看 Effect 在构造过程中,在不同场景下,是如何根据用户传参、Effect 之间关系,来确定自身属性的。
Effect 由 pushEffect 函数创建,传入的四个参数直接赋值给对应属性:
function pushEffect(tag, create, destroy, deps) { const effect: Effect = { tag, create, destroy, deps, }; // ... }
pushEffect 的调用入参,直接决定了 Effect 的内容。
初始化创建 Effect
在 mountEffectImpl 中,是对 Effect 的初始化创建: pushEffect(hookEffectTag, create, undefined, nextDeps)
,对应属性传值如下:
- tag:也就是 hookEffectTag,值为 mountEffect 传入的
UnmountPassive | MountPassive
=>0b10000000 | 0b01000000
=>0b11000000
- create:useEffect 的第一个入参函数
- destroy:传入一个 undefined,因为初始化创建的 Effect 不存在“前一次执行”
- deps:依赖数组
Effect 被执行
然后初始化创建的 Effect 会在 Fiber commit 的时候被执行,在 commitWork 阶段一个叫 commitHookEffectList 的方法中,对 Effect 做了这样的动作:
const create = effect.create; effect.destroy = create();
这时 Effect 的 destroy 被「暂时」挂上了自己 create 的返回。这就是前面说的「某些阶段来自于上一次同一个 Effect Hook 的 create 执行结果,某些阶段来自于自身。」
但这样不会乱吗?上一次 destroy 不就找不到了吗?不会。因为 Effect 总是先执行上一次 destroy,再执行自己的 create,此时上一次 destroy 已经执行过没用了,正好空出来 destroy 属性挂自己的,方便传给下一次 Effect。
更新创建 Effect
Fiber 更新阶段再执行 useEffect,来到 updateEffectImpl。这里首先会去拿上一次构造的 Effect,再看这张图:

新 Hook 被克隆出来,上一次 Hook 则留在 currentHook 指针上,通过 currentHook.memoizedState
拿到它的 Effect,以及Effect 的依赖(deps)和销毁函数(destroy,按前面所说,此时得到的 destroy 就是上一次 Effect 自己的)。
接着会做一件重要的事: 依赖对比 ,依赖是否变化,对当前 Effect 的处理方式影响很大。
依赖变化是怎么对比出来的?遍历 + 浅比较。
// areHookInputsEqual 方法 for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) { if (is(nextDeps[i], prevDeps[i])) { continue; } return false; } return true; // is 方法 return (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y);
当依赖有变化,创建的 Effect 属性值如下:
- tag:也就是 hookEffectTag,值为 updateEffect 传入的
UnmountPassive | MountPassive
=>0b10000000 | 0b01000000
=>0b11000000
- create:useEffect 的第一个入参函数
- destroy:上一次 Effect 的 destroy
- deps:依赖数组
当依赖没变化,Effect tag 会变成 NoHookEffect
=> 0b00000000
。
小结
至此我们摸清了在被 Fiber commit 处理前,Update Queue 中的 Effect 属性来源:
- tag:需要处理的时候(mount 或依赖有变的 update)为
0b11000000
,不需要处理的时候(依赖不变的 update)为0b00000000
- create:useEffect 的第一个入参函数
- destroy:上一次 Effect 的 destroy
- deps:依赖数组
处理 Fiber 上的 Effect
useEffect 产生的所有 Effect 都加入 Fiber 的 Update Queue,由 Fiber 在 commit 阶段统一处理。
在 commitRoot 入口,对 FunctionComponent 这样调用 commitHookEffectList 方法:
commitHookEffectList(UnmountPassive, NoHookEffect, finishedWork); commitHookEffectList(NoHookEffect, MountPassive, finishedWork);
其中前两个参数是常量 tag,用来和 Effect tag 比较判断 Effect 的处理方式,对应方法参数 unmountTag、mountTag;第三个参数 finishedWork 传入当前 Fiber。commitHookEffectList 被先后调了两次,从传参来看,NoHookEffect 是 0b00000000 置空,两次的 UnmountPassive(0b10000000) 和 MountPassive(0b01000000) 分别激活 unmountTag、mountTag,依次执行 umount 和 mount 操作。
commitHookEffectList 是统一处理 Update Queue 中 Effect 的入口,再回顾下 Update Queue 的结构:

队列里第一个 Effect 可以通过 const firstEffect = finishedWork.updateQueue.lastEffect.next
拿到,然后按照单向循环链表遍历:
// 遍历 Update Queue do { // 处理 Effect effect = effect.next; } while (effect !== firstEffect);
Unmount 处理
处理 Effect 的方式判断很巧妙,通过一个二进制位运算: (effect.tag & unmountTag) !== NoHookEffect
,判断是否需要进行 umount 处理。NoHookEffect 是个 0,那就要 effect.tag 和 unmountTag 不同且都不为 0,条件判断才为 true。结合 Effect 的属性值和 commitHookEffectList 传参内容,我们列举出以下几种情况:
- Effect 依赖没变(effect.tag = 0),则肯定不做 unmount 处理
- 第二次 commitHookEffectList(unmountTag = 0),也肯定不做 ummount 处理
- Effect 依赖有变(effect.tag = 0b11000000),且第一次 commitHookEffectList(unmountTag = 0b10000000),则做 unmount 处理:执行 effect.destroy(上一次 Effect 的销毁函数)
if ((effect.tag & unmountTag) !== NoHookEffect) { const destroy = effect.destroy; effect.destroy = undefined; if (destroy !== undefined) { destroy(); } }
Mount处理
也通过二进制来做: (effect.tag & mountTag) !== NoHookEffect
:
- Effect 依赖没变(effect.tag = 0),则肯定不做 mount 处理
- 第一次 commitHookEffectList(mountTag = 0),也肯定不做 mount 处理
- Effect 依赖有变(effect.tag = 0b11000000),且第二次 commitHookEffectList(mountTag = 0b01000000),则做 mount 处理:执行 effect.create(回调 useEffect 入参,并把返回作为 destroy 挂到 Effect 上)
if ((effect.tag & mountTag) !== NoHookEffect) { const create = effect.create; effect.destroy = create(); }
小结
这节介绍了 Effect Hook 实现:
- Effect Hook 来自 useEffect、useLayoutEffect。
- Effect Hook 通过 memoizedState 保存一个 useEffect 产生的 Effect 对象。
- Effect 对象保存着创建(create)回调、销毁(destroy)回调、依赖、处理标记。
- Effect 会同时挂到 Fiber 的 Update Queue 上,方便 Fiber 在 commit 阶段找到并执行,Update Queue 是个单向循环链表。
- 更新阶段,Effect 会找到同一调用在上一次构建的 Effect,对比依赖以决定被如何处理,并获取 destroy。
Part 5 其他 Hook
经过前面对 Hook 池和两种关键 Hook 的介绍,我们基本摸清了 Hook 的套路:调用 useXXX —> 构建 Hook 维护 Hook 池 —> 在 Hook 上加一些不同 useXXX 特有的数据和逻辑。以此类推,就很容易猜到其他 Hook 的实现方式了。
useMemo
useMemo 返回一个memoized 值。把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Memo Hook 上要存什么状态?依赖值和触发的计算值,实现上是一个数组。然后把计算值返回出去:
// mountMemo 和 updateMemo 方法 const nextValue = nextCreate(); hook.memoizedState = [nextValue, nextDeps]; return nextValue;
但在 update 阶段,上述逻辑是有条件的,即“依赖有变化”,否则只要返回上一次计算值就好。所以 useEffect 曾用到的 areHookInputsEqual 又出场了:
// updateMemo 方法 const prevState = hook.memoizedState; if (areHookInputsEqual(nextDeps, prevDeps)) { return prevState[0]; } const nextValue = nextCreate(); hook.memoizedState = [nextValue, nextDeps]; return nextValue;
useCallback
同 useMemo 十分相似,不同点是入参的方法不执行,变为直接存储:
// mountMemo 和 updateMemo 方法 hook.memoizedState = [callback, nextDeps]; return nextValue;
useRef
useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”,而这个“盒子”的引用值始终不变。
只要在 mount 时创建一个对象,存到 Hook 上,后续 update 直接取:
// mountRef const ref = {current: initialValue}; hook.memoizedState = ref; return ref; // updateRef return hook.memoizedState;
Part Z 总结
本篇介绍了 Hook 的实现原理:
- Function Component 中每次调用 useXXX,都会创建一个 Hook,这些 Hook 以 Hook 池的形式维护在 Fiber.memoizedState 上。
- Hook 是一个对象,上面存着对应调用的数据。不同 useXXX 方法在 Hook 上存的东西也不同。
- Function 初次执行和后续更新执行,Hook 池的维护方式、useXXX 要做的事大不相同,所以 React 会判断当前 Fiber 所处阶段(mount 或 update),换挡调用不同的 useXXX 实现。
- Fiber mount 时,Hook 被依次从头创建;update 时则逐个从旧 Hook 池中克隆,构造成新 Hook 池后再切换 Fiber.memoizedState,这样能保留旧 Hook,提供“新旧 Hook 对比”的能力。
- State Hook(useState、useReducer)在 Hook 对象上保存状态值和更新队列,update 阶段会清理更新队列并计算最新状态值。useState、useReducer 返回的 dispatch 方法用来把更新加入队列,并发起一次更新调度。
- Effect Hook(useEffect)在 Hook 对象上保存 Effect,一个记录副作用创建、销毁、依赖的对象。update 阶段构建新的 Effect,并对比新老 Hook 上 Effect 的依赖,决定 Effect 处理方式。Effect 会被同时记录在 Fiber 自身的更新队列里,等待 Fiber commit 后统一处理。
- React 原理系列 —— Hook 是这样工作的
- A100 买不到了,只有小显卡怎么训大模型
- MedISeg:面向医学图像语义分割的技巧、挑战和未来的方向
- CoRL 2022 | SurroundDepth: 自监督环视深度估计
- 【机器学习】逻辑回归(非常详细)
- dnn实践-特征处理
- Google数据安全自动化建设之路(白皮书)
- 用typescript类型来实现快排
- 基于自建 VTree 的全链路埋点方案
- 除了鲍威尔讲话,全球央行年会还揭露了什么?
- coost v3.0.0 (微型boost库)发布
- 仔细研究 Go(golang) 类型系统
- 干货 | 嵌入式数据分析最佳实践
- 关于高频量化交易的代码项目
- 彻底解决 qiankun 找不到入口的问题
- VIM 插件推荐
- 全网最通透:MySQL 的 redo log 保证数据不丢的原理
- 蚁群算法的简要分析
- 7月美联储会议纪要
- 容器平台架构之道