You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue 3 Options API:无关数据变更引发组件不必要重渲染问题

Vue 3 Options API 组件不必要重渲染问题分析与解决

问题场景

  • 子组件的prop值来自父组件的一个method
  • 该method无法替换为computed属性,因为需要对v-for循环中的特定项做处理后,再传值给对应的子组件
  • 当method返回数组/对象这类引用类型时,会触发子组件不必要的重渲染;返回字符串、数字这类原始类型时则不会出现此问题

复现方式

修改父组件中与子组件无关的数据属性(比如示例中的showMenu),子组件会触发beforeUpdate钩子,打印重渲染日志。

父组件代码

<template>
  <div>
    <div id="menu">
      <div @click="showMenu = !showMenu">Click Me</div>
      <div v-if="showMenu">
        打开控制台:修改无关属性本不应触发子组件重渲染,但由于在v-for中调用了返回数组的myMethod(chart),所以子组件会被重渲染。
      </div>
    </div>
    <div v-for="(chart, index) in items" :key="index">
      <MyComponent :table="myMethod(chart)" :title="chart.title" />
    </div>
  </div>
</template>
<script>
import MyComponent from './MyComponent.vue';
export default {
  components: {
    MyComponent,
  },
  data: function () {
    return {
      showMenu: false,
      items: [{ value: 1 }, { value: 2 }],
    };
  },
  methods: {
    myMethod(item) {
      // 去掉[]返回字符串就不会触发子组件全量重渲染
      return ['processed' + item.value];
    }
  }
};
</script>

子组件代码

<template>
  <div class="myComponent">
    {{ table }}
  </div>
</template>
<script>
export default {
  props: ['table'],
  beforeUpdate() {
    console.log('I have been rerendered');
  },
};
</script>
<style>
.myComponent {
  width: 10em;
  height: 4em;
  border: solid 2px darkblue;
}
</style>

问题原因

Vue判断组件是否需要重渲染时,会对比prop的新旧值:

  • 对于原始类型(字符串、数字、布尔等),采用值对比:只要值相同,就认为prop未变化
  • 对于引用类型(数组、对象),采用引用地址对比:即使内容完全相同,只要是新创建的引用(比如每次调用myMethod都返回新数组),Vue就会认为prop发生了变化,从而触发子组件重渲染

当父组件中showMenu这类无关属性变化时,父组件会触发重渲染,此时模板中调用myMethod(chart)会生成新的数组引用,导致子组件的table prop被判定为“变化”,进而触发不必要的重渲染。

解决方案

方案1:缓存处理后的结果(推荐)

在父组件中通过计算属性预先处理并缓存items的结果,避免每次渲染都生成新的引用:

<template>
  <div>
    <div id="menu">
      <div @click="showMenu = !showMenu">Click Me</div>
      <div v-if="showMenu">
        现在修改showMenu不会触发子组件重渲染了
      </div>
    </div>
    <div v-for="(item, index) in processedItems" :key="index">
      <MyComponent :table="item.table" :title="item.title" />
    </div>
  </div>
</template>
<script>
import MyComponent from './MyComponent.vue';
export default {
  components: { MyComponent },
  data() {
    return {
      showMenu: false,
      items: [{ value: 1 }, { value: 2 }],
    };
  },
  computed: {
    processedItems() {
      // 仅当items变化时,才重新生成处理后的数组和内部引用
      return this.items.map(item => ({
        ...item,
        table: ['processed' + item.value]
      }));
    }
  }
};
</script>

计算属性processedItems只有在依赖的items变化时才会重新计算,父组件其他属性变化时,子组件的table prop引用不会改变,因此不会触发重渲染。

方案2:子组件使用memo控制重渲染

通过Vue的memo高阶组件,让子组件仅在prop内容真正变化时才重渲染:

<!-- MyComponent.vue -->
<template>
  <div class="myComponent">
    {{ table }}
  </div>
</template>
<script>
import { memo } from 'vue';

export default memo({
  props: ['table', 'title'],
  beforeUpdate() {
    console.log('I have been rerendered');
  },
  // 自定义对比逻辑,可选;默认是浅对比
  shouldUpdate(newProps, oldProps) {
    // 对比数组内容是否一致
    if (newProps.table.length !== oldProps.table.length) return true;
    for (let i = 0; i < newProps.table.length; i++) {
      if (newProps.table[i] !== oldProps.table[i]) return true;
    }
    // 对比title是否变化
    return newProps.title !== oldProps.title;
  }
});
</script>

memo默认会对prop做浅对比,若需要深对比,可自定义shouldUpdate函数来判断是否需要重渲染。

方案3:将引用类型转为原始类型(仅适用于简单场景)

如果业务允许,可将数组/对象序列化为字符串传递,利用原始类型的值对比避免重渲染:

<!-- 父组件模板修改 -->
<MyComponent :table="JSON.stringify(myMethod(chart))" :title="chart.title" />

<!-- 子组件修改 -->
<script>
export default {
  props: ['table'],
  computed: {
    parsedTable() {
      return JSON.parse(this.table);
    }
  },
  beforeUpdate() {
    console.log('I have been rerendered');
  },
};
</script>

注意:此方法存在序列化/反序列化的性能开销,仅适合数据结构简单的场景。

内容的提问来源于stack exchange,提问作者daniel p

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 04:01:07