核心是“只更新该更新的,不碰不该碰的”:正确使用key(如item.id而非index)、虚拟列表(仅渲染可视区dom)、响应式局部更新、子组件memo化及精简props与事件,可避免无效重绘与错误复用。

核心是“只更新该更新的,不碰不该碰的”。虚拟 DOM 本身不直接减少长列表的 DOM 数量,但它配合正确的策略(尤其是 key 和 按需渲染逻辑),能极大抑制因数据变动引发的无效重绘、错误复用和整块重建。
用对 key:让 Diff 知道谁是谁
列表项必须带稳定、唯一、与数据身份绑定的 key(如数据库 ID、UUID),不能用数组索引。否则滚动、增删时,Diff 会误判节点复用关系,导致:
- 输入框失焦(实际 DOM 被替换而非移动)
- 动画中断或错位
- 已展开的折叠项意外收起
正确示例:v-for="item in list" :key="item.id";错误示例::key="index"。
配合虚拟列表:从根源砍掉冗余 DOM
虚拟 DOM 再聪明,也救不了渲染 10000 个 <li> 的页面。真正治本的方法是结合 虚拟列表(Virtual List):
- 只渲染当前可视区域 + 少量缓冲区的项(比如始终维持 20–30 个 DOM 节点)
- 用一个高占位元素(phantom)撑开滚动高度,保持滚动条可用
- 通过
scrollTop动态计算startIndex和endIndex,实时截取list.slice(startIndex, endIndex) - 用
transform: translateY()定位内容起始位置,视觉上“滚动”,实际 DOM 不增不减
避免触发全量 Diff 的写法
即使用了虚拟列表,不当的数据更新仍会让 Vue/React 误判整个子树需重算:
- 不要直接
list.push(newItem)后再this.list = [...this.list]—— 这会丢掉响应式依赖追踪 - 新增项应走响应式赋值:
this.list = [...this.list, newItem]或使用ref/reactive配合push(Vue 3) - 局部更新优先于全量替换:修改某一项字段,比
this.list = this.list.map(...)更轻量 - 复杂列表中,把每项封装为独立组件,并用
v-memo(Vue 3.2+)或React.memo控制子组件是否重渲染
精简 props 与事件绑定
每个列表项若携带大量无关 props 或内联函数,不仅增加虚拟节点体积,还可能让 Diff 失效:
- 只传当前项真正需要的字段,避免透传整个对象
- 事件处理器尽量复用,避免每次渲染都生成新函数:
@click="handleClick(item.id)"比@click="() => handleClick(item.id)"更安全 - 异步加载状态(如图片懒加载、骨架屏)用独立指令或组件封装,别混在主列表逻辑里










