如何减少 Vue 组件的重渲染?(如 `v-once`/`computed`/`shallowRef` 等)

减少 Vue 组件的重渲染是提升应用性能的关键。Vue 的响应式系统虽然强大,但如果使用不当,会导致不必要的组件更新。
以下是基于 Vue 3(部分适用于 Vue 2)的核心优化策略,涵盖了你提到的 v-oncecomputedshallowRef 以及其他关键技巧:

1. 静态内容优化:v-once

如果你确定某部分内容在初始化后永远不会改变,可以使用 v-once 指令。
  • 原理:告诉 Vue 跳过该节点及其子节点的后续更新检查。
  • 适用场景:静态文本、图标、不随状态变化的布局结构。
  • 代码示例
    html

    预览
    1<!-- 这个 span 只会渲染一次,即使 message 改变,这里也不会更新 -->
    2<span v-once>这是一个静态标题:{{ title }}</span>
    3
    4<!-- 整个组件只渲染一次 -->
    5<my-static-component v-once />
  • 注意:不要滥用,否则会导致数据无法更新。

2. 缓存计算属性:computed

避免在模板中直接进行复杂的逻辑运算或对象创建。
  • 原理computed 属性基于其响应式依赖进行缓存。只有当依赖项发生变化时,它才会重新求值。如果依赖未变,多次访问会直接返回之前的缓存结果。
  • 对比
    • ❌ Methods: 每次组件重渲染(即使无关数据变化),方法都会重新执行。
    • ✅ Computed: 仅当依赖变化时执行。
  • 代码示例
    javascript

    编辑
    1// ❌ 糟糕的做法:每次 render 都会执行过滤和排序
    2// <div v-for="item in filteredList()">{{ item.name }}</div>
    3// methods: { filteredList() { return this.list.filter(...) } }
    4
    5// ✅ 推荐做法:只有 list 变化时才重新计算
    6const filteredList = computed(() => {
    7  return props.list.filter(item => item.active).sort((a, b) => a.id - b.id);
    8});

3. 深层响应式优化:shallowRef 和 markRaw

这是 Vue 3 中针对大型对象或不需要深层响应式数据的强力优化手段。

A. shallowRef (浅层引用)

  • 原理:只追踪 .value 本身的赋值变化,深入追踪对象内部属性的变化。
  • 适用场景
    • 巨大的嵌套对象(如地图数据、大型图表配置),且你只需要在替换整个对象时触发更新。
    • 由外部库管理的状态对象(如 Redux store, Pinia 中的某些部分),Vue 不需要代理它们。
  • 代码示例
    javascript

    编辑
    1import { shallowRef, triggerRef } from 'vue';
    2
    3// 创建一个巨大的对象
    4const hugeData = shallowRef({ a: 1, b: { c: 2, d: 3 ... } });
    5
    6// 修改内部属性不会触发视图更新 (节省了大量 Proxy 开销)
    7hugeData.value.b.c = 999; // ❌ 视图不更新
    8
    9// 必须手动触发更新,或者替换整个对象
    10hugeData.value = { ...hugeData.value, b: { ...hugeData.value.b, c: 999 } }; // ✅ 触发更新
    11// 或者
    12triggerRef(hugeData); // ✅ 强制触发更新

B. markRaw (标记原始对象)

  • 原理:明确告诉 Vue 不要将某个对象转换为 Proxy(响应式对象)。
  • 适用场景
    • 第三方类实例(如 new Map()new Date(), 或复杂的 UI 库实例)。
    • 包含循环引用的对象。
  • 代码示例
    javascript

    编辑
    1import { reactive, markRaw } from 'vue';
    2
    3const state = reactive({
    4  user: { name: 'Alice' },
    5  // 如果 mapInstance 很大,转为 Proxy 会很慢且没必要
    6  mapInstance: markRaw(new Map()) 
    7});

4. 列表渲染优化:key 的正确使用

  • 原理:Vue 使用虚拟 DOM Diff 算法。key 帮助 Vue 识别哪些节点被添加、移除或移动。
  • 最佳实践
    • ✅ 必须使用唯一且稳定的 ID(如数据库 ID)。
    • ❌ 避免使用索引 (index) 作为 key:如果列表顺序会改变(排序、删除、插入),使用索引会导致 Vue 复用错误的 DOM 节点,引发不必要的重渲染甚至状态错乱。
  • 代码示例
    html

    预览
    1<!-- ✅ 正确 -->
    2<div v-for="item in items" :key="item.id">...</div>
    3
    4<!-- ❌ 错误 (如果 items 会排序或增删) -->
    5<div v-for="(item, index) in items" :key="index">...</div>

5. 事件处理优化:v-once 的兄弟 @once 与 防抖/节流

虽然 Vue 没有内置 @once 修饰符用于所有场景(早期版本有过讨论,目前主要靠逻辑控制),但可以通过以下方式减少事件触发导致的重渲染:
  • 逻辑控制:在事件处理函数内部添加标志位,防止重复提交或频繁触发。
  • 工具库:使用 lodash.debounce 或 lodash.throttle 包装事件处理函数,减少高频事件(如 scrollresizeinput)触发的更新频率。

6. 组件层级优化

  • 拆分组件:将大组件拆分为小组件。父组件的状态变化不会导致子组件重渲染,除非子组件接收的 props 发生了变化。
  • v-memo (Vue 3.2+)
    • 类似于 v-once,但可以指定依赖数组。只有当依赖数组中的值发生变化时,模板的一部分才会被更新。
    • 适用场景:极其昂贵的列表项渲染,且只有特定 prop 变化时才需要重绘。
    html

    预览
    1<div v-for="item in list" :key="item.id" v-memo="[item.id, item.selected]">
    2  <!-- 只有 id 或 selected 变化时,这块 DOM 才会 diff/更新 -->
    3  <complex-component :data="item" />
    4</div>

7. 避免常见的“陷阱”

  • 不要在模板中定义对象/数组
    html

    预览
    1<!-- ❌ 每次渲染都会创建新对象,导致子组件认为 props 变了而重渲染 -->
    2<child :config="{ theme: 'dark', size: 'large' }" />
    3
    4<!-- ✅ 使用 computed 或常量 -->
    5<child :config="staticConfig" />
  • 慎用 v-if vs v-show
    • 如果需要频繁切换显示状态,用 v-show(只改 CSS display,不销毁重建 DOM)。
    • 如果初始化时条件为假且很少变为真,用 v-if(减少初始渲染开销)。

总结建议表

表格

场景 推荐方案 核心作用
纯静态内容 v-once 彻底跳过更新检查
复杂逻辑/衍生数据 computed 依赖缓存,避免重复计算
超大对象/外部实例 shallowRef / markRaw 减少 Proxy 开销,避免深层监听
列表渲染 稳定的 :key 优化 Diff 算法,减少 DOM 操作
昂贵子树渲染 v-memo 细粒度控制模板更新
频繁切换显隐 v-show 避免 DOM 销毁和重建
通过组合使用这些策略,可以显著降低 Vue 应用的运行时开销,特别是在处理大量数据或复杂交互的场景下。

购买须知/免责声明
1.本文部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责。
2.若您需要商业运营或用于其他商业活动,请您购买正版授权并合法使用。
3.如果本站有侵犯、不妥之处的资源,请在网站右边客服联系我们。将会第一时间解决!
4.本站所有内容均由互联网收集整理、网友上传,仅供大家参考、学习,不存在任何商业目的与商业用途。
5.本站提供的所有资源仅供参考学习使用,版权归原著所有,禁止下载本站资源参与商业和非法行为,请在24小时之内自行删除!
6.不保证任何源码框架的完整性。
7.侵权联系邮箱:aliyun6168@gail.com / aliyun666888@gail.com
8.若您最终确认购买,则视为您100%认同并接受以上所述全部内容。

会员源码网 建站教程 如何减少 Vue 组件的重渲染?(如 `v-once`/`computed`/`shallowRef` 等) https://svipm.com/21322.html

相关文章

猜你喜欢