前段时间让 AI 帮我写一个可编辑表格组件。
React 版本的,每输入一个字符,整个表格闪一下。Vue 版本的,打开页面后请求发了五六遍。
AI 修了三轮——改 memo、改 key、改依赖数组——最后它放弃了,告诉我:"建议手动检查渲染逻辑。"
这件事让我意识到一个问题:AI 写代码的速度在飞涨,但对"响应式"这件事的理解,它连门都没入。
这篇文章不讨论"AI 会不会取代前端",咱们落地一点——拿 AI 最容易翻车的三个"无效渲染"场景,看看它到底在哪里摔倒,以及看懂原理的人为什么能把它扶起来。
雷区 1:渲染粒度 —— 更新一个人,重绘一张表
React 翻车现场
我让 AI 生成一个员工列表,支持点选行高亮。
AI 很快给出了这段代码:
function EmployeeList() {const [employees, setEmployees] = useState(data);const [selectedId, setSelectedId] = useState(null);return (<ul>{employees.map(emp => (<lionClick={() => setSelectedId(emp.id)}className={selectedId === emp.id ? 'active' : ''}>{emp.name} — {emp.dept}</li>))}</ul>);}
看一遍逻辑没问题。但跑起来发现:每次点击高亮一行,整张列表 100 行全部重渲染。 原因很简单——selectedId 一变,函数组件整体重新执行,所有 <li> 全部重建。
AI 不会告诉你:React 的默认行为是自上而下全量渲染。
修复也不复杂,但难点在于你得知道"这里需要防一下":
const EmployeeItem = React.memo(({ emp, isActive, onClick }) => (<lionClick={onClick}className={isActive ? 'active' : ''}>{emp.name} — {emp.dept}</li>));function EmployeeList() {// ...state 不变return (<ul>{employees.map(emp => (<EmployeeItemkey={emp.id}emp={emp}isActive={selectedId === emp.id}onClick={() => setSelectedId(emp.id)}/>))}</ul>);}
React.memo 告诉 React:"props 没变就别重渲染这个子组件。"就这么一层薄薄的防护,100 行的列表从"全量刷新"变成了"仅高亮那行变动"。
Vue 翻车现场
同样的功能让 AI 写 Vue 版本,它给出的是:
<script setup>
const selectedId = ref(null)
const employees = ref(data)
const activeEmployee = computed(() => {
return employees.value.find(e => e.id === selectedId.value)
})
</script>
<template>
<ul>
<li v-for="emp in employees" :key="emp.id"
@click="selectedId = emp.id"
:class="{ active: selectedId === emp.id }">
{{ emp.name }} — {{ emp.dept }}
</li>
</ul>
</template>
这段代码在渲染层面比 React 的初版好——Vue 的 v-for 自带细粒度更新,不会整表重绘。但 AI 在这里踩了另一个坑:它写了一个 computed 叫 activeEmployee,实际又没用上。这是个"幽灵计算"——每次 selectedId 变化,computed 都要遍历一遍员工数组。
更隐蔽的问题是:如果 AI 把获取员工数据的请求写在了 computed 里(有人真遇到过),那每次选中行都会触发一次 API 调用。
原理挖一挖
React 和 Vue 的渲染哲学本质不同:
- React
:组件是一棵函数树。状态变了 → 整棵树重新执行(re-render)→ Virtual DOM diff → 找出真正要改的 DOM。优化思路是"减少重新执行的节点"(memo、useMemo)。 - Vue
:组件是一张依赖网。状态变了 → 精确通知依赖了它的节点 → 直接更新对应 DOM。优化思路是"避免依赖链过长"(合理拆分 computed、避免 deep watch)。
两种哲学各有优劣,但 AI 对这两种哲学的理解都是零。它只知道"模板",不知道"代价"。
雷区 2:列表渲染 —— 缺了 key,整表重绘
React & Vue 翻车现场(症状一样)
这个雷区太经典了。AI 生成列表时,key 的写法几乎永远是:
// React - AI 最爱{items.map((item, index) => <likey={index}>{item.name}</li>)}<!-- Vue - AI 最爱 --><liv-for="(item, index) in items":key="index">{{ item.name }}</li>
如果列表是静态的,index 做 key 确实能用。但一旦列表会增删改排,index 做 key 的后果是灾难性的——本来只需要删除一行,结果因为 key 错位,整个列表的 DOM 全部重建。
这就是为什么很多人反馈"AI 写的列表交互一多就卡",根源就在这里。
修复极其简单:
// React{items.map(item => <likey={item.id}>{item.name}</li>)}<!-- Vue --><liv-for="item in items":key="item.id">{{ item.name }}</li>
原理挖一挖
key 不是"性能优化的可选配置项",它是 Virtual DOM 的复用标识。
diff 算法在对比新旧两棵虚拟节点树时,靠 key 来判断"这是同一个节点挪了位置"还是"这是新插入的节点"。
用 index 做 key,等于告诉 diff 算法:"我放弃唯一标识,你用位置来猜吧。"增删操作一多,一猜就错。
AI 不会告诉你:key 用对了,diff 是 O(n);key 用错了,diff 退化成 O(n³)。
雷区 3:副作用管理 —— 依赖数组里的幽灵
React 翻车现场
让 AI 加一个功能:"当筛选条件变化时,重新请求数据"。
它写出来的代码大概长这样:
const [filters, setFilters] = useState({ dept: '', level: '' });useEffect(() => {fetchEmployees(filters).then(setEmployees);}, []); // 依赖数组为空?!
AI 的逻辑是:"我说了在筛选变化时重新请求,所以 useEffect 里写了,但为什么依赖数组是空的?"
因为 AI 在生成代码时是按"模式匹配"而非"逻辑推理"来写的。它记住了"useEffect 传空数组 = 只在挂载时执行"这个模式,却没有推理出这个场景下 filters 应该放在依赖里。
正确的修复:
useEffect(() => {fetchEmployees(filters).then(setEmployees);}, [filters]); // ✅ 依赖 filters,变化时重新请求更隐蔽的变体是 AI 写出了 stale closure:useEffect(() => {const timer = setInterval(() => {console.log(filters); // 永远是最初的值}, 3000);return () => clearInterval(timer);}, []); // filters 没在依赖里!
Vue 翻车现场
Vue 版本的问题更隐蔽。AI 可能写出:
watch(filters, (newVal) => {fetchEmployees(newVal).then(setEmployees)}, { deep: true, immediate: true })
这段代码单看没问题。但 AI 接着又在同一组件里写了另一个 watch:
watch(employees, (newVal) => {filters.dept = newVal[0]?.dept // 在 watch 里改 watch 的源头})
两个 watch 互相触发 → 死循环。Vue 不会崩溃,但你的页面会一直发请求直到用户关掉标签页。
原理挖一挖
React 的 useEffect:它是一个"声明式的副作用契约"。依赖数组是契约里的条款——你告诉 React"这些变量变了才执行我"。AI 不理解的恰恰是"为什么要把变量列出来",它看不到闭包捕获的陷阱。
Vue 的 watch:它是"显式的观察者"。问题不在于 watch 本身,而在于"观察者模式中最忌讳的事情——在回调里修改被观察的对象"。AI 没有"副作用纯度"的概念。
AI 时代的"不变量"
回顾这三个雷区:
AI 能在一分钟内生成一个完整组件。但它理解不了"为什么这个组件跑起来又卡又慢"——因为它没有心智模型。
前端的价值正在从"写"转向"审"(review + 架构决策)。 你不需要比 AI 写得快,但你需要知道 AI 写的东西哪些是对的、哪些是错的。看懂渲染,是你的定价权。
下次 AI 给你生成了一段代码,别直接复制粘贴。花 30 秒看一下:memo 加了吗?key 对吗?依赖填齐了吗?
它不懂的事,你来兜底。
本文是"AI 时代的硬通货"系列第一期。如果你也想聊聊哪些前端底层原理让你觉得"懂了就是不一样",欢迎留言。
夜雨聆风