- 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
: HooksDispatcherOnUpdateWho is current?
current: Fiber of the last commitworkInProgress: The Fiber of this render
Conclusion
| scene | current |
|---|---|
| first mount | null |
| Later updates | Point 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
| mount | update | |
|---|---|---|
| hook source | new | clone |
| 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 #3update 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
| Vue3 | React |
|---|---|
| effect is created once | render can be multiple times |
| Automatic dependency tracking | Order implicit agreement |
| effect ≠ render | render = 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"