# Step 65: 手写 mini-React

一句话导读:JSX 只是 h() 调用,函数组件只是返回元素,useState 靠调用顺序存状态,最后 reconcile 成 step64 的 box/text 树。


# 一、这一步做了什么(What)

step65 在 step64 的渲染引擎前面接了一个玩具版 React:h 作为 JSX factory,函数组件产出元素树,useState 保存组件状态,evaluate 把元素树求值成宿主树,再交给 step64 的 diff 渲染器刷屏。

这一步仍不接主程序,只是理解 React/Ink 的接缝。

# 二、面试官视角:为什么要做?(Why)

面试题:既然 step64 已经能渲染节点树,为什么还要接 React?

因为手写节点树适合 demo,不适合复杂 UI。真实 Claude Code 的终端界面有消息列表、权限框、工具卡、状态栏、输入区,必须靠组件化管理。React 提供的不是“网页能力”,而是一套 UI 状态组织模型。

直接写 box/text JSX + 组件
嵌套深、难复用 组件拆分清晰
状态手动拼 useState 驱动重渲染
UI 与数据混杂 props/state → UI

# 三、原理:它是怎么工作的(How)

JSX factory 在 step65/ink/react/miniReact.ts:

export function h(type, props, ...children): Element {
  return { type, props: props ?? {}, children };
}
1
2
3

hooks 靠数组和游标:

let hookStates: any[] = [];
let hookIndex = 0;

export function useState<T>(initial: T) {
  const i = hookIndex++;
  hookStates[i] ??= initial;
  const setState = (v) => { hookStates[i] = typeof v === "function" ? v(hookStates[i]) : v; schedule(); };
  return [hookStates[i], setState];
}
1
2
3
4
5
6
7
8
9

reconcile 是递归求值:

function evaluate(node: Child): Host | string | null {
  if (typeof el.type === "function") {
    return evaluate(el.type({ ...el.props, children: el.children }));
  }
  // box/text → host node
}
1
2
3
4
5
6

数据流:

<Counter />
  → h(Counter, ...)
  → 调用 Counter,useState 取状态
  → 返回 <box><text>...</text></box>
  → evaluate 成 box/text 宿主树
  → step64 diff 渲染
1
2
3
4
5
6

# 四、深入追问(面试常见 follow-up)

Q:hooks 为什么不能写在 if 里?
A:因为本实现和真实 React 一样依赖“调用顺序稳定”。条件分支会改变 hookIndex 对应关系,状态就串位。

Q:为什么 setState 后从根重算还能接受?
A:教学版上游整棵重算,但下游 step64 行级 diff 只重画变化行。真实 React 用 fiber 做更细粒度 reconcile。

Q:JSX 有魔法吗?
A:没有。/** @jsx h */ 让编译器把 JSX 转成 h(type, props, children),它只是对象构造语法糖。

Q:Fragment 为什么可以简化?
A:教学重点是组件和 hooks。真实 Fragment 要参与 reconcile;这里不深用,保留占位即可。

# 五、踩坑 / 设计权衡

这个 mini-React 的 hook 状态是全局数组,不支持多组件实例独立生命周期,也没有 effect 清理。它是为了看懂机制,不是为了替代 React。

# 六、与真实源码的对照

我们的实现 Claude Code 源码
h + evaluate react-reconciler HostConfig
全局 hookStates fiber 上的 hook 链表
从根重算 fiber 增量 reconcile + 调度
只有 useState useEffect/useMemo/useContext 等完整 hooks

# 七、一句话总结

React 的核心可以压缩成三件事:JSX 生成元素对象,组件和 hooks 生成带状态的 UI,reconcile 把元素提交给宿主渲染器。

# 下一节预告

下一篇是 step66:路线从手写终端 UI 转向 Web,把 QueryEngine 包成 HTTP + SSE 后端服务。