Mini React Runtime 实现解析(Fiber + Hooks)
在这篇博客中,我将分享一个最小化 React Runtime 的实现,包括 Fiber 架构和 Hooks 的原理。本文适合想深入理解 React 内部工作机制的前端开发者。
引言
React 的核心是 虚拟 DOM + Fiber + Hooks。
- Fiber:运行时的组件实例,负责管理状态、hooks 和调度。
- Hooks:函数组件的状态管理机制,通过调用顺序保证状态稳定性。
- 虚拟 DOM:描述 UI 的对象,不直接渲染,Fiber 根据它构建真实 DOM。
本文实现的是一个最小可运行版本,帮助理解原理。
整体架构
架构图
graph TD
A[React Element] -->|createFiber| B[Fiber 节点]
B -->|child/sibling 链表| C[Fiber 树]
C -->|performUnitOfWork| D[Function Component 执行]
D -->|调用 Hooks| E[Hooks 状态存储在 Fiber.hooks]
B -->|createDom| F[真实 DOM 节点]
C -->|commitEffects| G[useEffect 执行]
H[更新操作] -->|setState/useMemo 等| B
E -->|读取/修改| D
F -->|挂载到父 DOM| DOM[页面 DOM]
style A fill:#f9f,stroke:#333,stroke-width:1px
style B fill:#bbf,stroke:#333,stroke-width:1px
style C fill:#bfb,stroke:#333,stroke-width:1px
style D fill:#ffb,stroke:#333,stroke-width:1px
style E fill:#fbf,stroke:#333,stroke-width:1px
style F fill:#fbb,stroke:#333,stroke-width:1px
style G fill:#bff,stroke:#333,stroke-width:1px
style H fill:#eee,stroke:#333,stroke-width:1px
执行顺序
flowchart TD
%% 初次渲染
A["render(TodoApp)"] --> B["创建根 Fiber"]
B --> C["performUnitOfWork(根 Fiber)"]
C --> D{"Fiber.type 是否为函数组件?"}
D -- 是 --> E["设置 wipFiber=当前 Fiber, hookIndex=0, hooks=[]"]
E --> F["执行 TodoApp(props)"]
%% useState
F --> G["调用 useState(0)"]
G --> H["检查 oldHook 是否存在"]
H --> I{"首次渲染? (oldHook 为空)"}
I -- 是 --> J["创建新 Hook {state=0, queue=[]}"]
I -- 否 --> K["应用队列更新计算最新 state"]
J --> L["hookIndex++ 返回 state, setState"]
K --> L
%% useMemo
L --> M["调用 useMemo(() => count*2, [count])"]
M --> N["检查 oldHook 是否存在并比较 deps"]
N --> O{"依赖是否变化?"}
O -- 是 --> P["执行 factory() 计算新 value"]
O -- 否 --> Q["复用上一次 value"]
P --> R["hookIndex++ 返回 memo value"]
Q --> R
%% useEffect
R --> S["调用 useEffect(..., [count])"]
S --> T["检查 oldHook 是否存在并比较 deps"]
T --> U{"依赖是否变化?"}
U -- 是 --> V["将 effect 放入 pendingEffects 队列"]
U -- 否 --> W["不收集 effect"]
V --> X["hookIndex++"]
W --> X
%% 返回 JSX
X --> Y["返回子 Element: div > h3/p/button"]
Y --> Z["reconcileChildren 创建子 Fiber"]
Z --> AA{"fiber.child 是否存在?"}
AA -- 是 --> C
AA -- 否 --> AB["处理 sibling 或回到 parent"]
AB --> AC["commit 阶段: 挂载 DOM"]
AC --> AD["执行 pendingEffects 中的 useEffect"]
%% 用户点击 button
AD --> AE["用户点击 button 调用 setCount(c => c+1)"]
AE --> AF["入队更新 Hook.queue"]
AF --> AG["rerender() 重置 hookIndex=0, 执行 TodoApp"]
AG --> G
style A fill:#f9f,stroke:#333
style B fill:#bbf,stroke:#333
style C fill:#bfb,stroke:#333
style D fill:#ffb,stroke:#333
style E fill:#fbf,stroke:#333
style F fill:#fbb,stroke:#333
style G fill:#bff,stroke:#333
style H fill:#ffe,stroke:#333
style I fill:#eef,stroke:#333
style J fill:#ddd,stroke:#333
style K fill:#ccd,stroke:#333
style L fill:#ffd,stroke:#333
style M fill:#cff,stroke:#333
style N fill:#ecf,stroke:#333
style O fill:#fcf,stroke:#333
style P fill:#ffc,stroke:#333
style Q fill:#eef,stroke:#333
style R fill:#ddf,stroke:#333
style S fill:#fdd,stroke:#333
style T fill:#def,stroke:#333
style U fill:#ffd,stroke:#333
style V fill:#cfc,stroke:#333
style W fill:#eee,stroke:#333
style X fill:#fdf,stroke:#333
style Y fill:#f9f,stroke:#333
style Z fill:#bbf,stroke:#333
style AA fill:#bfb,stroke:#333
style AB fill:#ffb,stroke:#333
style AC fill:#fbf,stroke:#333
style AD fill:#fbb,stroke:#333
style AE fill:#bff,stroke:#333
style AF fill:#ffe,stroke:#333
style AG fill:#def,stroke:#333
Element 层(虚拟 DOM)
js
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child =>
typeof child === "object" ? child : createTextElement(child)
),
},
};
}
function createTextElement(text) {
return {
type: "TEXT",
props: {
nodeValue: text,
children: [],
},
};
}
createElement:生成虚拟 DOM 对象createTextElement:将字符串或数字包装成文本节点- 所有子节点统一成对象形式,方便 Fiber 遍历
Fiber 层(运行时实例)
js
function createFiber(element, parent) {
return {
type: element.type,
props: element.props,
parent,
child: null,
sibling: null,
dom: null,
alternate: null,
hooks: null,
};
}
function createDom(fiber) {
if (fiber.type === "TEXT") return document.createTextNode(fiber.props.nodeValue || "");
const dom = document.createElement(fiber.type);
Object.keys(fiber.props)
.filter(key => key !== "children")
.forEach(name => dom[name] = fiber.props[name]);
return dom;
}
- 每个 Fiber 对应一个组件或 DOM 节点
alternate用于保存上一次渲染的 Fiber,实现状态复用hooks保存当前组件的所有 hooks
Render 阶段(Fiber 构建)
js
function performUnitOfWork(fiber) {
if (typeof fiber.type === "function") {
wipFiber = fiber;
hookIndex = 0;
wipFiber.hooks = [];
const children = [fiber.type(fiber.props)];
reconcileChildren(fiber, children);
} else {
if (!fiber.dom) fiber.dom = createDom(fiber);
const parentDom = getParentDom(fiber);
if (parentDom) parentDom.appendChild(fiber.dom);
reconcileChildren(fiber, fiber.props.children || []);
}
if (fiber.child) return fiber.child;
let next = fiber;
while (next) {
if (next.sibling) return next.sibling;
next = next.parent;
}
}
- 对 Function Component 执行组件函数并收集 hooks
- 对 DOM 节点创建真实 DOM 并挂载
- 深度优先遍历 Fiber 树
Hooks 实现
useState
js
function useState(initialState) {
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
const hook = { state: oldHook ? oldHook.state : initialState, queue: [] };
(oldHook?.queue || []).forEach(action => {
hook.state = typeof action === "function" ? action(hook.state) : action;
});
const setState = action => { hook.queue.push(action); rerender(); };
wipFiber.hooks.push(hook);
hookIndex++;
return [hook.state, setState];
}
- state 保存在 Fiber.hooks 中
- 通过 hookIndex 保证顺序
- setState 入队并触发 rerender
useEffect
js
function useEffect(effect, deps) {
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
let hasChanged = !oldHook || !deps || deps.some((d,i) => !Object.is(d, oldHook.deps[i]));
const hook = { effect, deps, cleanup: oldHook?.cleanup };
if (hasChanged) pendingEffects.push(hook);
wipFiber.hooks.push(hook);
hookIndex++;
}
- render 阶段收集 effect
- commit 阶段统一执行
- deps 不变时跳过
useRef
js
function useRef(initialValue) {
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
const hook = { current: oldHook ? oldHook.current : initialValue };
wipFiber.hooks.push(hook);
hookIndex++;
return hook;
}
- 返回稳定引用
- 修改 current 不触发 rerender
useMemo
js
function useMemo(factory, deps) {
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
const hasChanged = !oldHook || !deps || deps.some((d,i) => !Object.is(d, oldHook.deps[i]));
const hook = { value: hasChanged ? factory() : oldHook.value, deps };
wipFiber.hooks.push(hook);
hookIndex++;
return hook.value;
}
- 缓存计算结果
- deps 不变时返回上次结果
useContext
js
function useContext(context) {
const hook = { value: context._currentValue };
wipFiber.hooks.push(hook);
hookIndex++;
return hook.value;
}
- 读取 context._currentValue
- 这是一个极简实现,不支持嵌套 Provider
Demo 示例
js
function TodoApp() {
const [count, setCount] = MiniReact.useState(0);
const double = MiniReact.useMemo(() => count * 2, [count]);
MiniReact.useEffect(() => console.log(count), [count]);
return MiniReact.createElement(
"div",
null,
MiniReact.createElement("h3", null, "Count: ", count),
MiniReact.createElement("p", null, "Double: ", double),
MiniReact.createElement(
"button",
{ onclick: () => setCount(c => c + 1) },
"Add"
)
);
}
MiniReact.render(MiniReact.createElement(TodoApp, null), document.getElementById("root"));
- 演示了 useState、useEffect、useMemo
- 每次点击按钮都会触发 rerender
总结
通过这个 Mini React Runtime,我们可以理解:
- Fiber 树的作用:管理组件实例和 hooks
- Hooks 的原理:依赖调用顺序和 Fiber 存储
- render 阶段 vs commit 阶段:render 只是构建 Fiber,不操作 DOM;commit 才真正更新 DOM
- 最小化实现足够教学,可以逐步扩展支持 diff 算法、异步渲染、useLayoutEffect 等
这是理解 React Hooks 和 Fiber 内部机制的绝佳入门案例。
可以仔细阅读下面源码注释写的非常清楚
js
/**************************************************************
* Mini React Runtime (Fiber + Hooks)
*
* 这是一个教学用途的「最小可运行 React 内核」
* 目标:帮助你**完整理解 React Fiber + Hooks 的运行机制**
*
* 特点:
* - 同步 render(无并发、无时间切片)
* - Fiber 树结构
* - Hooks 基于「调用顺序」实现
* - 支持 useState / useEffect / useRef / useMemo / useContext
**************************************************************/
/**************************************************************
* 全局运行时状态(非常关键)
**************************************************************/
let wipFiber = null;
// 当前正在 render 的 Function Component 对应的 Fiber
// Hooks 的所有读写都依赖它
let hookIndex = 0;
// 当前组件 render 过程中,第几个 hook
// useState/useEffect/... 全部依赖「调用顺序」
let currentRoot = null;
// 已经 commit 的根 Fiber
// rerender 时作为 alternate(旧 Fiber 树)
// 用来保存已经提交的Fiber树,下一次render时候把新的rootFiber的alternate指向它
let pendingEffects = [];
// render 阶段收集的所有 useEffect
// 会在 commit 阶段统一执行
/**************************************************************
* Element 层(虚拟 DOM)
**************************************************************/
/**
* createElement
* 用于创建 React Element(虚拟节点)
*
* 等价于:
* React.createElement(type, props, ...children)
*
* React Element 是一个「纯描述对象」
* 并不会直接参与渲染
*/
function createElement(type, props, ...children) {
return {
type, // string(div)或 function(组件)
props: {
...props,
// children 必须统一成 object
children: children.map(child =>
typeof child === "object"
? child
: createTextElement(child)
),
},
};
}
/**
* createTextElement
* 将字符串 / 数字包装成 TEXT 类型的 Element
*/
function createTextElement(text) {
return {
type: "TEXT",
props: {
nodeValue: text,
children: [],
},
};
}
/**************************************************************
* Fiber 层(运行时实例)
**************************************************************/
/**
* createFiber
*
* Fiber 是 React 运行时的核心数据结构
* 每一个 Fiber ≈ 一个组件/DOM 实例
*/
function createFiber(element, parent) {
return {
type: element.type, // 组件函数 or DOM 标签
props: element.props, // props
parent, // 父 Fiber
child: null, // 第一个子 Fiber
sibling: null, // 下一个兄弟 Fiber
dom: null, // 对应的真实 DOM
alternate: null, // 上一次 render 的 Fiber
hooks: null, // hooks 数组(Function Component 专属)
};
}
/**
* createDom
* 根据 Fiber 创建真实 DOM 节点
*/
function createDom(fiber) {
// 文本节点
if (fiber.type === "TEXT") {
return document.createTextNode(fiber.props.nodeValue ?? "");
}
// 普通 DOM 节点
const dom = document.createElement(fiber.type);
// 将 props 映射到 DOM 上(事件、属性)
Object.keys(fiber.props)
.filter(key => key !== "children")
.forEach(name => {
dom[name] = fiber.props[name];
});
return dom;
}
/**************************************************************
* Fiber 构建(reconcile)
**************************************************************/
/**
* reconcileChildren
*
* 根据 element 列表构建 Fiber.child / Fiber.sibling 链表
*
* 同时:
* - 尝试复用 oldFiber(alternate)
* - 保证 hooks 能够跨 render 复用
*/
function reconcileChildren(fiber, elements = []) {
let index = 0;
let prevSibling = null;
// 上一次 render 的第一个子 Fiber
let oldFiber = fiber.alternate && fiber.alternate.child;
while (index < elements.length) {
const element = elements[index];
if (!element) {
index++;
continue;
}
// 为 element 创建新的 Fiber
const newFiber = createFiber(element, fiber);
// 关联旧 Fiber(用于 hooks / state 复用)
if (oldFiber) {
newFiber.alternate = oldFiber;
oldFiber = oldFiber.sibling;
}
// 构建 child / sibling 链表
if (index === 0) {
fiber.child = newFiber;
} else {
prevSibling.sibling = newFiber;
}
prevSibling = newFiber;
index++;
}
}
/**
* getParentDom
* 向上查找最近的 Host Fiber(有 dom 的 Fiber)
*/
function getParentDom(fiber) {
let parent = fiber.parent;
while (parent && !parent.dom) {
parent = parent.parent;
}
return parent ? parent.dom : null;
}
/**************************************************************
* Render 阶段(Fiber 构建)
**************************************************************/
/**
* performUnitOfWork
*
* Fiber 构建的核心调度函数
* 采用深度优先遍历(DFS)
*/
function performUnitOfWork(fiber) {
/******** Function Component ********/
if (typeof fiber.type === "function") {
// 标记当前正在 render 的 Fiber
wipFiber = fiber;
// hooks 从 0 开始
hookIndex = 0;
// 初始化 hooks 容器
wipFiber.hooks = [];
// 执行组件函数(render 阶段)
const children = [fiber.type(fiber.props)];
// 根据返回的 element 构建子 Fiber
reconcileChildren(fiber, children);
}
/******** Host Component(DOM) ********/
else {
// 创建 DOM
if (!fiber.dom) {
fiber.dom = createDom(fiber);
}
// 挂载到父 DOM
const parentDom = getParentDom(fiber);
if (parentDom) {
parentDom.appendChild(fiber.dom);
}
// 继续向下构建
reconcileChildren(fiber, fiber.props.children || []);
}
// 深度优先遍历
if (fiber.child) return fiber.child;
let next = fiber;
while (next) {
if (next.sibling) return next.sibling;
next = next.parent;
}
}
/**************************************************************
* render 入口
**************************************************************/
function render(element, container) {
const rootFiber = {
dom: container,
props: { children: [element] },
parent: null,
child: null,
sibling: null,
alternate: currentRoot, // 旧 Fiber 树
};
currentRoot = rootFiber;
let nextUnitOfWork = rootFiber;
while (nextUnitOfWork) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
// commit 阶段
commitEffects();
}
/**
* rerender
* 所有 setState 的最终触发点
*/
function rerender() {
const container = currentRoot.dom;
container.innerHTML = "";
render(currentRoot.props.children[0], container);
}
/**************************************************************
* Hooks 实现
**************************************************************/
/**
* useState
*
* 原理总结:
* 1. state 存储在 fiber.hooks 中
* 2. 通过 hookIndex 保证顺序一致
* 3. setState 只是入队,不立即更新
* 4. rerender 时统一计算新 state
*/
function useState(initialState) {
// 找到旧 hook
const oldHook =
wipFiber.alternate &&
wipFiber.alternate.hooks &&
wipFiber.alternate.hooks[hookIndex];
// 初始化 hook
const hook = {
state: oldHook ? oldHook.state : initialState,
queue: [],
};
// 执行上一次 setState 入队的 action
const actions = oldHook ? oldHook.queue : [];
actions.forEach(action => {
// 获取最新状态
hook.state =
typeof action === "function"
? action(hook.state)
: action;
});
// setState 实现
const setState = action => {
// 向之前的wipFiber对应的hook的queue中添加action
hook.queue.push(action);
rerender();
};
wipFiber.hooks.push(hook);
hookIndex++;
return [hook.state, setState];
}
/**
* useEffect
*
* 核心思想:
* - render 阶段只收集
* - commit 阶段统一执行
* - deps 控制是否重新执行
*/
function useEffect(effect, deps) {
const oldHook =
wipFiber.alternate &&
wipFiber.alternate.hooks &&
wipFiber.alternate.hooks[hookIndex];
let hasChanged = true;
// deps 对比
if (oldHook && deps) {
hasChanged = deps.some(
(dep, i) => !Object.is(dep, oldHook.deps[i])
);
}
const hook = {
effect, // 副作用函数
deps, // 依赖
cleanup: oldHook ? oldHook.cleanup : null,
};
if (hasChanged) {
pendingEffects.push(hook);
}
wipFiber.hooks.push(hook);
hookIndex++;
}
/**
* commitEffects
* effect 的真正执行阶段
*/
function commitEffects() {
pendingEffects.forEach(hook => {
// 先执行上一次 cleanup
if (hook.cleanup) {
hook.cleanup();
}
// 再执行 effect
const cleanup = hook.effect();
// 保存 cleanup
hook.cleanup = typeof cleanup === "function" ? cleanup : null;
});
pendingEffects = [];
}
/**
* useRef
*
* 特点:
* - 返回稳定引用
* - 修改 current 不会触发 render
*/
function useRef(initialValue) {
const oldHook =
wipFiber.alternate &&
wipFiber.alternate.hooks &&
wipFiber.alternate.hooks[hookIndex];
const hook = {
current: oldHook ? oldHook.current : initialValue,
};
wipFiber.hooks.push(hook);
hookIndex++;
return hook;
}
/**
* useMemo
*
* 作用:
* - 缓存计算结果
* - deps 不变时复用
*/
function useMemo(factory, deps) {
const oldHook =
wipFiber.alternate &&
wipFiber.alternate.hooks &&
wipFiber.alternate.hooks[hookIndex];
let hasChanged = true;
if (oldHook && deps) {
hasChanged = deps.some(
(dep, i) => !Object.is(dep, oldHook.deps[i])
);
}
const hook = {
value: hasChanged ? factory() : oldHook.value,
deps,
};
wipFiber.hooks.push(hook);
hookIndex++;
return hook.value;
}
/**************************************************************
* Context(极简版)
**************************************************************/
function createContext(defaultValue) {
const context = {
_currentValue: defaultValue,
Provider: null,
};
context.Provider = function Provider(props) {
context._currentValue = props.value;
return props.children;
};
return context;
}
function useContext(context) {
const hook = { value: context._currentValue };
wipFiber.hooks.push(hook);
hookIndex++;
return hook.value;
}
/**************************************************************
* 对外 API
**************************************************************/
const MiniReact = {
createElement,
render,
useState,
useEffect,
useRef,
useMemo,
useContext,
createContext,
};
/**************************************************************
* Demo
**************************************************************/
function TodoApp() {
const [count, setCount] = MiniReact.useState(0);
const double = MiniReact.useMemo(() => {
console.log("compute memo");
return count * 2;
}, [count]);
MiniReact.useEffect(() => {
console.log("effect:", count);
}, [count]);
return MiniReact.createElement(
"div",
null,
MiniReact.createElement("h3", null, "Count: ", count),
MiniReact.createElement("p", null, "Double: ", double),
MiniReact.createElement(
"button",
{ onclick: () => setCount(c => c + 1) },
"Add"
)
);
}
MiniReact.render(
MiniReact.createElement(TodoApp, null),
document.getElementById("root")
);
/**************************************************************
* TodoApp 初始化流程(精细 Fiber + Hooks 注释)
**************************************************************/
/**
* ======================= Step 1: render() 被调用 =======================
* MiniReact.render(element, container)
* container = <div id="root"></div>
*
* 创建根 Fiber:
* rootFiber = {
* dom: container,
* props: { children: [TodoApp element] },
* parent: null,
* child: null,
* sibling: null,
* alternate: null
* }
*
* currentRoot = rootFiber
* nextUnitOfWork = rootFiber
*/
/**
* ======================= Step 2: performUnitOfWork(rootFiber) =======================
* fiber = rootFiber
* fiber.type = undefined / 容器 DOM
*
* 1. 创建 DOM:
* fiber.dom = container
* parentDom = null (fiber.parent = null)
*
* 2. reconcileChildren(fiber, [TodoApp element])
* - index=0
* - 创建 TodoApp Fiber:
* todoFiber = {
* type: TodoApp,
* props: {},
* parent: rootFiber,
* child: null,
* sibling: null,
* dom: null,
* alternate: null,
* hooks: null
* }
* - rootFiber.child = todoFiber
*
* nextUnitOfWork = todoFiber
*/
/**
* ======================= Step 3: performUnitOfWork(todoFiber) =======================
* fiber = todoFiber
* fiber.type = TodoApp (函数组件)
*
* 1. 准备 render 函数组件:
* wipFiber = todoFiber
* hookIndex = 0
* wipFiber.hooks = [] <-- 新 Fiber 的 hooks 数组初始化
*
* 2. 执行 TodoApp():
* const [count, setCount] = useState(0)
*
* ======================= Step 3a: useState =======================
* oldHook = null (首次渲染,wipFiber.alternate = null)
* 新 hook:
* 0: { state: 0, queue: [] }
* wipFiber.hooks = [ { state:0, queue:[] } ]
* hookIndex++ => 1
* 返回 count = 0, setCount = function
*
* ======================= Step 3b: useMemo =======================
* const double = useMemo(() => count*2, [count])
* oldHook = null
* hasChanged = true (首次渲染)
* 新 hook:
* 1: { value: 0, deps:[0] }
* wipFiber.hooks = [
* { state:0, queue:[] },
* { value:0, deps:[0] }
* ]
* hookIndex++ => 2
* double = 0
*
* ======================= Step 3c: useEffect =======================
* const effect = () => { console.log(count); }
* oldHook = null
* hasChanged = true
* 新 hook:
* 2: { effect: effectFn, deps:[0], cleanup: null }
* pendingEffects = [ hook ]
* wipFiber.hooks = [
* { state:0, queue:[] },
* { value:0, deps:[0] },
* { effect:effectFn, deps:[0], cleanup:null }
* ]
* hookIndex++ => 3
*
* 3. TodoApp 返回 JSX:
* <div>
* <h3>Count: 0</h3>
* <p>Double: 0</p>
* <button>Add</button>
* </div>
*
* reconcileChildren(todoFiber, [div element])
* 创建 divFiber、h3Fiber、pFiber、buttonFiber
* 建立 child/sibling 链表
*/
/**
* ======================= Step 4: commit 阶段 =======================
* 遍历 Fiber 树,挂载 DOM:
* rootFiber.dom.appendChild(todoFiber.dom) -> divFiber.dom -> h3/p/button
*
* 执行 pendingEffects:
* hook.effect() -> console.log(count) 输出 0
* hook.cleanup = null
* pendingEffects 清空
*
* 最终状态:
* currentRoot = rootFiber
* wipFiber = null
* hookIndex = 0
* Fiber 树:
* ROOT
* └── TodoApp Fiber
* └── divFiber
* ├── h3Fiber
* ├── pFiber
* └── buttonFiber
* Hooks 状态:
* 0: { state:0, queue:[] }
* 1: { value:0, deps:[0] }
* 2: { effect: effectFn, deps:[0], cleanup:null }
*/
/**************************************************************
* TodoApp 第一次点击按钮更新流程(精细 Fiber + Hooks 注释)
**************************************************************/
/**
* ======================= Step 1: 用户点击按钮 =======================
* 点击 button 执行 onclick:
* setCount(c => c + 1)
*
* 1. 查找当前 Hook:
* wipFiber = null (尚未进入 render)
* hookIndex = 当前调用位置 0
* 对应旧 Hook:
* oldHook = currentRoot.child.hooks[0] = { state:0, queue:[] }
*
* 2. 入队 action 到 hook.queue:
* action = c => c + 1
* oldHook.queue.push(action)
*
* Hooks 状态(旧 Fiber 的 hooks,不变):
* 0: { state:0, queue:[c=>c+1] }
* 1: { value:0, deps:[0] }
* 2: { effect:effectFn, deps:[0], cleanup:null }
*
* 3. 调用 rerender()
* 清空根 DOM: container.innerHTML = ""
* 准备重新 render(currentRoot.props.children[0], currentRoot.dom)
*/
/**
* ======================= Step 2: performUnitOfWork(todoFiber) =======================
* wipFiber = todoFiber
* hookIndex = 0
* wipFiber.hooks = [] <-- 新 Fiber hooks 重建
*
* 执行 TodoApp():
*/
/**
* ======================= Step 2a: useState =======================
* hookIndex = 0
* oldHook = { state:0, queue:[c=>c+1] }
*
* 新 hook:
* state = oldHook.state = 0
* queue = [] (新 hook 的 queue 临时为空)
*
* 处理旧 queue:
* for action in oldHook.queue:
* state = action(state) = (0+1) = 1
* 新 hook.state = 1
*
* hookIndex++ => 1
* wipFiber.hooks = [
* { state:1, queue:[] } <-- 更新后的 useState
* ]
*
* 返回 count = 1, setCount = function
*/
/**
* ======================= Step 2b: useMemo =======================
* hookIndex = 1
* oldHook = { value:0, deps:[0] }
*
* 检查 deps:
* 旧 deps = [0], 新 count = 1
* deps 变化 -> hasChanged = true
*
* 重新计算 value = count*2 = 1*2 = 2
* 新 hook:
* { value:2, deps:[1] }
* hookIndex++ => 2
* wipFiber.hooks = [
* { state:1, queue:[] },
* { value:2, deps:[1] }
* ]
*
* double = 2
*/
/**
* ======================= Step 2c: useEffect =======================
* hookIndex = 2
* oldHook = { effect: effectFn, deps:[0], cleanup:null }
*
* 检查 deps:
* old deps=[0], count=1 -> deps变化 -> hasChanged = true
*
* 新 hook:
* { effect:effectFn, deps:[1], cleanup:null }
* pendingEffects.push(newHook)
* hookIndex++ => 3
* wipFiber.hooks = [
* { state:1, queue:[] },
* { value:2, deps:[1] },
* { effect:effectFn, deps:[1], cleanup:null }
* ]
*/
/**
* ======================= Step 3: JSX 返回 & Fiber 树复用 =======================
* TodoApp 返回:
* <div>
* <h3>Count: 1</h3>
* <p>Double: 2</p>
* <button>Add</button>
* </div>
*
* reconcileChildren(todoFiber, [div element])
* Fiber 树复用:
* todoFiber.child = divFiber (复用旧节点)
* divFiber.child = h3Fiber -> pFiber -> buttonFiber (复用旧节点)
*
* dom 不重建,只更新 textContent
*/
/**
* ======================= Step 4: commit 阶段 =======================
* 更新 DOM:
* h3.textContent = "Count:1"
* p.textContent = "Double:2"
*
* 执行 pendingEffects:
* hook.effect() -> console.log(count) 输出 1
* alert("Count is: 1")
* hook.cleanup = null
* pendingEffects 清空
*/
/**
* ======================= Step 5: 最终状态 =======================
* currentRoot = rootFiber
* wipFiber = null
* hookIndex = 0
*
* Fiber 树:
* ROOT
* └── TodoApp Fiber
* └── divFiber
* ├── h3Fiber
* ├── pFiber
* └── buttonFiber
*
* Hooks 状态:
* 0: { state:1, queue:[] } <-- useState 已处理 queue
* 1: { value:2, deps:[1] } <-- useMemo 更新
* 2: { effect:effectFn, deps:[1], cleanup:null } <-- useEffect 更新
*
* pendingEffects = []
*/