如何防止使用render函数的Vue.js组件触发不必要的子组件重渲染
Vue手写render函数触发全量重渲染的解决方案
问题根因
你遇到的问题本质是手写render函数丢失了模板编译器的自动优化能力:Vue的模板编译器会在编译阶段自动做静态节点标记、动态节点拆分、key自动生成等优化,只有确实依赖变化的节点才会重渲染;而手写h函数生成的VNode默认没有这些优化标记,组件任意响应式状态变更都会触发整棵VNode树的全量Diff,进而导致所有节点重渲染。
可落地的解决方法
优先拆分独立子组件
这是性价比最高的方案:把筛选栏、数据列表、侧边栏等独立模块分别封装成单独的子组件,子组件只接收自身需要的props。只要子组件的props没有发生变化、内部也没有触发更新的响应式依赖,即使父组件重渲染,子组件也会直接跳过更新流程,完全不会触发重渲染。该方案对slot内容同样适用:将slot对应的逻辑封装成子组件传入即可。
给模块根节点设置稳定唯一的key
如果不想拆分组件,可以给每个独立模块的根VNode设置固定不变的key,比如筛选栏的key设为'filter-bar'、数据列表的key设为'data-list'。Diff算法会优先比对key,只要key稳定不变、节点属性也没有变化,就会直接复用原有节点,不会触发重渲染。该规则对slot的包裹容器同样生效,只要容器的key稳定,未发生变更的slot内容不会被重渲染。
注意:不要用随机值、时间戳等每次渲染都会变化的值作为key,否则会导致节点强制销毁重建,反而加重性能负担。
使用框架提供的缓存API
- Vue2环境下,可以给静态内容的VNode添加
once: true属性,等效于模板的v-once指令,该内容只会渲染一次,后续永远不会触发重渲染。 - Vue3环境下,可以使用
memo选项自定义更新条件:h(Component, { memo: [dep1, dep2] }, children),只有memo数组内的依赖发生变化时,该节点才会重新渲染,粒度比key更精细。
- Vue2环境下,可以给静态内容的VNode添加
隔离响应式状态作用域
将筛选条件相关的状态尽量放在筛选栏组件内部维护,只有需要跨模块共享的状态才提升到父组件,从根源上缩小状态变更触发的更新范围。
常见注意事项
- 如果slot内容是在父组件作用域内定义的,且内容依赖了父组件的响应式状态,那父组件状态变更时slot内容更新是正常行为,可通过将slot内容封装为子组件、添加memo配置的方式优化。
- 开发环境下Vue会附加很多调试校验逻辑,重渲染的感知会比生产环境更明显,优化后建议在生产环境验证实际效果。
内容的提问来源于stack exchange,提问作者PaReeOhNos
相关产品推荐
相关产品推荐

