Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
Join the discussion
Loading comments...
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
了解 React 如何将 Hooks 管理为链表、处理状态更新并重新渲染。
Hooks 以单向链表的形式存储在 Fiber 节点上。当组件渲染时,React 按顺序遍历此列表 — 将每个 hook 调用(useState、useEffect、useRef 等)与其存储的 state 匹配。这就是 Hooks 规则存在的原因:如果你有条件地调用 hooks,列表顺序会改变,React 会为错误的 hook 读取错误的 state。
每个 hook 调用(useState、useEffect、useRef、useMemo…)都创建一个带有 memoizedState 字段和 next 指针的 Hook 对象。React 按调用顺序将每个对象追加到列表中。该列表存储在 Fiber 节点的 memoizedState 字段上。
useState hook 对象存储:memoizedState(当前值)、queue(待处理 setState 调用的链表)和 dispatch(你在组件中使用的 setState 函数)。dispatch 函数是稳定的 — 它在渲染之间永不改变。
当你调用 setState(newValue) 时,React 不会立即更新 state。而是将一个 Update 对象追加到 hook 的队列中,并调度 Fiber 的重新渲染。同一事件处理器中的多个 setState 调用会被批处理为一次重新渲染。
在下次渲染时,React 再次遍历 hooks 列表。对于每个 useState hook,它按顺序处理所有排队的更新(或调用 useReducer 的 reducer 函数)。最终计算的值成为新的 memoizedState。
effect hook 对象存储:create(你传递的回调)、destroy(上次返回的清理函数)、deps(依赖数组)和一个标签,指示是 useEffect、useLayoutEffect 还是 useInsertionEffect。
Hooks 以单向链表形式存储在 Fiber 节点上。调用顺序 = 列表顺序。条件 hooks 会破坏列表。
Hook 对象上存储当前值的字段。对于 useState:state 值。对于 useRef:'{' current: value '}'。对于 useMemo:缓存结果。
每个 hook 对象上存储待处理 setState 调用的循环链表。在每批结束时刷新 — 所有更新按顺序应用。
React 根据渲染阶段切换 hook 实现。第一次渲染时:HooksDispatcherOnMount。重新渲染时:HooksDispatcherOnUpdate。这就是为什么 hooks 不能在组件外调用。
只在顶层调用 hooks(绝不在条件/循环内),且只从 React 函数调用。这些规则存在是因为 React 通过 hooks 在链表中的位置来识别它们。
1// Your component:2function Counter() {3 const [count, setCount] = useState(0); // Hook 14 const [name, setName] = useState('Bob'); // Hook 25 const ref = useRef(null); // Hook 367 useEffect(() => { ... }, [count]); // Hook 48}910// React's internal hooks linked list for this fiber:11Hook1 { memoizedState: 0, next: Hook2 } // count12Hook2 { memoizedState: 'Bob', next: Hook3 } // name13Hook3 { memoizedState: { current: null }, next: Hook4 } // ref14Hook4 { memoizedState: { create, destroy, deps: [0] }, next: null }1516// If you conditionally call hook 2:17// Hook1 → Hook3 → Hook4 (Hook2 slot is now useRef!)18// React reads "Bob" as the ref's current value → BUG
理解 hooks 内部机制解释了为什么 Hooks 规则不是任意限制,而是技术必要性。它还解释了为什么 useState setter 函数总是稳定的(它附加到队列,而不是值),为什么 hooks 跨渲染工作(它们持久化在 Fiber 上),以及为什么你可以构建自定义 hooks — 它们只是调用其他 hooks 的函数,向列表添加更多节点。
You add an early return in a component for a loading state: `if (loading) return <Spinner />`. After the data loads, React crashes with 'Rendered more hooks than during the previous render.'
The early return is ABOVE some hook calls. On the first render (loading=true), React processes 2 hooks. On the second render (loading=false), it processes 5 hooks. React's linked list has 2 slots but encounters 5 hook calls — the list is corrupted.
Move ALL hook calls above any conditional returns. The early return must come AFTER the last hook call. React hooks are a linked list indexed by call order — the call count must be identical across every render of the same component.
Takeaway: The 'Rules of Hooks' aren't arbitrary restrictions — they're a direct consequence of the linked list data structure. React identifies hooks by position (call order), not by name. Changing the number of hooks between renders corrupts the list.
You build a custom `useToggle` hook used in 3 different components. You notice that toggling in one component doesn't affect the others — but you're confused about WHY, since they share the same hook code.
Developers sometimes assume custom hooks share state like a global singleton. They expect `useToggle()` in Component A and Component B to share the same boolean value, leading to incorrect architecture decisions.
Each component has its own fiber node with its own hooks linked list. When Component A calls `useToggle()`, React creates a hook node in Component A's list. Component B gets a completely separate hook node in its own list. Custom hooks are just a code-sharing mechanism — state is always local to the calling component's fiber.
Takeaway: Custom hooks share LOGIC, not STATE. Each component instance gets its own copy of the hook's state because each fiber maintains its own hooks linked list. To share state between components, you need Context, a state manager, or lifting state up.