Skip to content
Created: September 21, 2025
  • Hooks can distinguish mount / update, not by Fiber.tag, but by "whether current Fiber exists"

1. What is Hooks Dispatcher? ​

Essential sentence ​

Hooks Dispatcher = a set of function tables "which implementation of useX should be executed during the current render"

ts
const HooksDispatcherOnMount = {
  useState: mountState,
  useEffect: mountEffect,
  useMemo: mountMemo,
}

const HooksDispatcherOnUpdate = {
  useState: updateState,
  useEffect: updateEffect,
  useMemo: updateMemo,
}

⚠️ useState itself is just:

ts
export function useState(initialState) {
  return ReactCurrentDispatcher.current.useState(initialState)
}

👉 Which set of logic is actually executed depends on the dispatcher

2. Where is the Dispatcher switched? ​

Answer: renderWithHooks

Key judgments of renderWithHooks ​

ts
ReactCurrentDispatcher.current =
  current === null
    ? HooksDispatcherOnMount
    : HooksDispatcherOnUpdate

Who is current? ​

  • current: Fiber of the last commit
  • workInProgress: The Fiber of this render

Conclusion ​

scenecurrent
first mountnull
Later updatesPoint to old Fiber

👉 This is the only condition for mount / update switching

3. Why can’t we use Fiber.tag to judge? ​

You might be thinking:

"Isn't a FunctionComponent always a FunctionComponent?"

The reason is:

  • mount/update is the time dimension
  • Fiber.tag is the type dimension
txt
同一个 FunctionComponent
第一次 render  → mount
第二次 render → update

👉 tag can’t be distinguished at all

4. What is the difference between the two sets of logic of mount / update? ​

We use useState as a representative.

1️⃣ mountState: Create Hook ​

ts
function mountState(initialState) {
  const hook = mountWorkInProgressHook()

  hook.memoizedState = initialState
  hook.queue = createUpdateQueue()

  const dispatch = dispatchSetState.bind(
    null,
    currentlyRenderingFiber,
    hook.queue
  )

  return [hook.memoizedState, dispatch]
}

The core tasks of mount ​

  • Create hook node
  • Create hook list
  • initialize state
  • Create updateQueue

2️⃣ updateState: Reuse Hook ​

ts
function updateState() {
  const hook = updateWorkInProgressHook()
  const queue = hook.queue

  let newState = hook.memoizedState

  // 处理 updateQueue
  const pending = queue.pending
  if (pending !== null) {
    // 计算新 state
  }

  hook.memoizedState = newState
  return [newState, queue.dispatch]
}

The core tasks of update ​

  • Find old hooks in order
  • Clone to new Fiber
  • Perform state updates

3️⃣ The most essential difference between the two ​

mountupdate
hook sourcenewclone
Read queue❌✅
Whether to consume update❌✅

5. Why can’t the order of Hooks be changed?(root cause) ​

Now you should be able to push it out yourself 👇

During render: ​

ts
useState()   // hook #1
useEffect()  // hook #2
useMemo()    // hook #3

update phase: ​

ts
updateWorkInProgressHook()
  ↕
currentHook.next

👉 Wrong order = Right or wrong hook

ts
if (cond) {
  useState() // ❌
}
  • When mounting: may execute
  • When updating: may not be executed
  • The linked list is directly misaligned

6. Why is "mount executed twice" under StrictMode? ​

This is a good extension point.

React 18 development mode: ​

  • mount → unmount → mount (emulation)
  • The purpose is to detect side effects

⚠️ NOTE:

  • Dispatcher or mount
  • Fiber was discarded and rebuilt
  • Won’t go update

7. Fundamental differences from Vue effect ​

Vue3React
effect is created oncerender can be multiple times
Automatic dependency trackingOrder implicit agreement
effect ≠ renderrender = effect

8. One sentence summary ​

  • Hooks Dispatcher can be switched because React is in renderWithHooks,
  • Use "whether current Fiber exists" as the time anchor to decide whether the Hook should be "created" or "reused"