# 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 };
}
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];
}
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
}
2
3
4
5
6
数据流:
<Counter />
→ h(Counter, ...)
→ 调用 Counter,useState 取状态
→ 返回 <box><text>...</text></box>
→ evaluate 成 box/text 宿主树
→ step64 diff 渲染
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 后端服务。