- renderWithHooks 不是“挂载函数”,而是 render 阶段“接管 Fiber 执行权”的入口
一、整体鸟瞰(先画流程)
一次函数组件更新的大致路径:
txt
scheduleUpdateOnFiber
↓
performConcurrentWorkOnRoot
↓
renderRoot
↓
workLoop
↓
performUnitOfWork
↓
beginWork(fiber)
↓
updateFunctionComponent
↓
renderWithHooks 👈 关键点👉 renderWithHooks 就是在 beginWork 阶段被调用的
二、Fiber 上“挂”了什么?
Fiber 本身并不“保存 renderWithHooks”
Fiber 上保存的是:
ts
fiber.type // 组件函数
fiber.memoizedState // hooks 链表
fiber.updateQueue // effect 队列👉 renderWithHooks 是执行逻辑,不是属性
三、beginWork:Fiber 执行的入口
简化版伪代码:
ts
function beginWork(current, workInProgress) {
switch (workInProgress.tag) {
case FunctionComponent:
return updateFunctionComponent(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps
)
}
}四、updateFunctionComponent 做了什么?
ts
function updateFunctionComponent(
current,
workInProgress,
Component,
props
) {
// 1. 准备上下文
prepareToReadContext(workInProgress)
// 2. 重点:执行组件函数
const children = renderWithHooks(
current,
workInProgress,
Component,
props
)
// 3. diff children
reconcileChildren(current, workInProgress, children)
return workInProgress.child
}👉 renderWithHooks 在这里“接管 render”
五、renderWithHooks 内部到底干了什么?
一句话版
- 把当前 Fiber 设为“全局 Hook 上下文”,然后执行组件函数
1️⃣ 关键全局变量(重点)
ts
let currentlyRenderingFiber = null
let workInProgressHook = null
let currentHook = null⚠️ 注意:
- Hooks 是靠全局变量 + 调用顺序工作的
- 这也是“Hooks 不能写在条件里”的根本原因
2️⃣ renderWithHooks 伪代码
ts
function renderWithHooks(
current,
workInProgress,
Component,
props
) {
currentlyRenderingFiber = workInProgress
workInProgress.memoizedState = null
workInProgress.updateQueue = null
// 区分 mount / update
ReactCurrentDispatcher.current =
current === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate
const children = Component(props)
// render 结束,清理
currentlyRenderingFiber = null
workInProgressHook = null
currentHook = null
return children
}👉 核心:把 Fiber 暴露给 Hooks
六、useState 是怎么“写到 Fiber 上的”?
Mount 阶段
ts
function useState(initialState) {
return mountState(initialState)
}ts
function mountState(initialState) {
const hook = mountWorkInProgressHook()
hook.memoizedState = initialState
hook.queue = createUpdateQueue()
return [hook.memoizedState, dispatch]
}ts
function mountWorkInProgressHook() {
const hook = { memoizedState: null, next: null }
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = hook
} else {
workInProgressHook.next = hook
}
workInProgressHook = hook
return hook
}👉 Hook 链表挂在 fiber.memoizedState
Update 阶段
ts
function updateWorkInProgressHook() {
const hook = clone(currentHook)
workInProgressHook.next = hook
currentHook = currentHook.next
workInProgressHook = hook
}👉 靠调用顺序对齐
七、useEffect 又是怎么“挂”的?
ts
function mountEffect(create, deps) {
const hook = mountWorkInProgressHook()
const effect = {
create,
deps,
destroy: null
}
pushEffect(effect)
}ts
function pushEffect(effect) {
const fiber = currentlyRenderingFiber
fiber.flags |= Passive
fiber.updateQueue.push(effect)
}- 👉 effect 不在 hook 链表里执行
- 👉 而是挂在 Fiber.updateQueue,commit 阶段跑
八、对比 Vue effect
| Vue3 | React |
|---|---|
| activeEffect | currentlyRenderingFiber |
| effect(fn) | renderWithHooks |
| dep → effect | fiber.memoizedState |
| scheduler | Scheduler + commit |
九、为什么 renderWithHooks 必须在 Fiber 上?
因为:
- Hooks 状态必须 跟组件实例绑定
- render 可中断 / 重跑
- Fiber 是唯一稳定载体
- Hooks 不是组件私有变量,而是 Fiber 状态机的一部分
十、renderWithHooks 的本质:
- 在 render 阶段,把 Fiber 暂时“提升”为全局上下文,让 Hooks 有地方读写状态