mini-react最小实现手把手带你看清hooks本质

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,我们可以理解:

  1. Fiber 树的作用:管理组件实例和 hooks
  2. Hooks 的原理:依赖调用顺序和 Fiber 存储
  3. render 阶段 vs commit 阶段:render 只是构建 Fiber,不操作 DOM;commit 才真正更新 DOM
  4. 最小化实现足够教学,可以逐步扩展支持 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 = []
 */
相关推荐
2503_928411564 小时前
12.26 小程序问题和解决
前端·javascript·微信小程序·小程序
灼华_4 小时前
超详细 Vue CLI 移动端预览插件实战:支持本地/TPGZ/NPM/Git 多场景使用(小白零基础入门)
前端
借个火er4 小时前
npm/yarn/pnpm 原理与选型指南
前端
总之就是非常可爱4 小时前
vue3 KeepAlive 核心原理和渲染更新流程
前端·vue.js·面试
Mr_chiu4 小时前
当AI成为你的前端搭子:零门槛用Cursor开启高效开发新时代
前端·cursor
over6974 小时前
防抖与节流:前端性能优化的“双子星”,让你的网页丝滑如德芙!
前端·javascript·面试
red润4 小时前
手把手封装Iframe父子单向双向通讯功能
前端·javascript·vue.js
gustt4 小时前
JavaScript 闭包实战:手写防抖与节流函数,优化高频事件性能
前端·javascript·面试
止水编程 water_proof4 小时前
JQuery 基础
前端·javascript·jquery