With AI, You Barely Need a Frontend Framework
核心观点
传统的论点认为像"Vamp"这样的方法太冗长。当人类编写每一行代码时,这种观点是成立的。但当AI代理编写(或修改)代码时,这个观点就不适用了。
对AI来说,重要的是显式性、本地性和可预测性,而不是简洁性。
核心模式
1. Templates - 模板
使用模板字符串将状态渲染为HTML:
class CounterView {
constructor(container, dispatch, initialState) {
container.innerHTML = sanitize`
<div>
<button data-ref="dec">−</button>
<span data-ref="count">${initialState.count}</span>
<button data-ref="inc">+</button>
</div>
`;
// 绑定事件...
}
}
2. Bindings - 绑定
引入sync函数和绑定来更新DOM:
this.b.bindText("count", (s) => String(s.count));
sync(state: State): void {
this.b.sync(state);
}
3. Composition - 组合
使用bindChild嵌套子视图:
this.b.bindChild(navRef, NavView, (s) => ({
loggedIn: s.auth.status === "logged-in",
}), (childMsg) => dispatch({ type: "NAV_MSG", msg: childMsg }));
4. Conditional Children - 条件子元素
使用bindSlot条件挂载/卸载:
this.b.bindSlot(detailRef, (s) => {
if (!s.selectedId) return undefined;
return show(DetailView, { id: s.selectedId }, () => {});
});
5. Lists - 列表
使用key来跟踪列表项:
this.b.bindList(listRef, "li", (s) =>
s.items.map((item) => showKeyed(item.id, ItemView, { item }, () => {}))
);
6. Unique data-refs - 唯一引用
ref()生成唯一的data-ref,防止父子视图冲突:
const countRef = ref("count"); // 返回类似 "count_0"
const incRef = ref("inc"); // 返回类似 "inc_1"
7. Styles - 样式
使用cls()生成唯一类名,mountStyle()注入:
const headerClass = cls("header");
mountStyle(`.${headerClass} { padding: 1rem; }`);
container.innerHTML = `<div class="${headerClass}">...</div>`;
为什么现在可行?
对AI而言:
- 显式性:没有通过代理或其他魔法产生的远程操作
- 本地性:模式是显式和重复的,AI可以可靠地模式匹配
- 可预测性:没有隐藏的机制(fiber调度器、钩子链表、协调启发式)
整个生命周期清晰可见:
class MyView {
constructor(...) {
// before mount
this.content.innerHTML = `...`
// after mount but before children mount
this.b.bindSlot(...
}
sync(...) {
// before update
this.b.sync(...)
// after update
}
destroy(...) {
this.b.cleanup();
}
}
附录:React的问题
React Hooks
本质上,React使用链表来跟踪钩子。钩子在链表中的位置是React建立钩子对应关系的唯一方式。因此钩子列表必须是确定性的——钩子必须始终以相同的顺序出现。
React Fiber与并发渲染
React 16引入了fiber概念,本质上将在react树中的渲染任务分解为称为fiber的独立部分。这允许渲染被中断,并分解为更小的片段,防止UI锁定。
问题:由于渲染现在是异步的,并分部分应用,大大复杂化了DOM测量、焦点管理、撕裂和动画计时。
性能
React的VDOM渲染和diff操作以昂贵著称。许多团队采用的解决方案是调优shouldUpdate调用,或引入immer来提供不可变状态,以便框架可以根据对象引用相等性来短路更新。
结论
这个模式被描述在一个300行的markdown文件和一个300行的绑定库/助手TypeScript文件中。你可以复制粘贴库到你的项目中,将markdown文件添加到你的agent的上下文窗口中,然后就可以开始了。
你永远不需要再接触另一个框架了。