vue3的组件优化

前言

vue的组件优化大家可能不太关注,但是当一个组件的节点过多,更新一个数据的话就可能导致整个组件render,这是不必要的性能浪费,接下来就带大家直击现场

一、组件性能损耗?

全量patch

这里我写了一个案例,来直观感受一下render的消耗时长

javascript 复制代码
<template>
  <div style="padding: 20px">
    <h2>10,000 字段全量更新测试</h2>
    <p>请输入内容并观察控制台:</p>

    <div style="margin-bottom: 20px; color: red; font-weight: bold">
      检测到数据变更,正在触发全量 Diff...
    </div>

    <div v-for="key in keys" :key="key" style="margin-bottom: 5px">
      <label>{{ key }}: </label>
      <input
        :value="formData[key]"
        @input="(e) => onFieldInput(key, e.target.value)"
      />
    </div>
  </div>
</template>

<script setup>
import { reactive, onUpdated } from "vue";

// 1. 生成 10,000 个字段
const keys = Array.from({ length: 10000 }, (_, i) => `field_${i}`);
const formData = reactive(Object.fromEntries(keys.map((k) => [k, ""])));

// 使用普通变量,不要用 ref,避免触发额外的响应式追踪
let renderStartTime = 0;
let isBenchmarking = false;

const onFieldInput = (key, value) => {
  if (!isBenchmarking) {
    // 记录开始时间:数据变化前的一瞬间
    renderStartTime = performance.now();
    isBenchmarking = true;
    console.log("--- 渲染开始 ---");
  }

  // 改变数据,触发父组件重新渲染
  formData[key] = value;
};

onUpdated(() => {
  if (isBenchmarking) {
    const renderEndTime = performance.now();
    const duration = (renderEndTime - renderStartTime).toFixed(2);

    console.log(
      `%c[Result] 10,000个节点全量 Patch 耗时: ${duration}ms`,
      "color: white; background: red; padding: 4px 8px; font-weight: bold;",
    );

    // 渲染完成后重置锁
    isBenchmarking = false;
    renderStartTime = 0;
  }
});
</script>

我在同一个组件内渲染1w个input标签,给每一个input v-model一个数据

此时当我们修改任意一个input看看结果

我们发现父组件进行了全量patch,耗时大概十几毫秒

组件拆分

接下来,我们将这个input组件拆分成子组件

javascript 复制代码
<template>
  <div style="padding: 20px">
    <h2>10,000 字段【原子化优化】实验</h2>
    <p>请输入内容,你会发现父组件<b>完全不触发</b>更新:</p>

    <div style="margin-bottom: 20px; color: green; font-weight: bold">
      父组件状态:静默(不参与渲染比对)
    </div>

    <div v-for="key in keys" :key="key" style="margin-bottom: 5px">
      <AtomicInput :name="key" />
    </div>
  </div>
</template>

<script setup>
import { reactive, provide, onUpdated, ref } from "vue";
import AtomicInput from "./AtomicInput.vue"; // 引入下面的子组件
const a = ref(0);
const keys = Array.from({ length: 10000 }, (_, i) => `field_${i}`);
const formData = reactive(Object.fromEntries(keys.map((k) => [k, ""])));

// 1. 提供数据源和更新方法
provide("formContext", {
  state: formData,
  update: (key, val) => {
    formData[key] = val;
    handleChange();
  },
});
const handleChange = () => {
  console.log(formData);
};
// 2. 这里的 onUpdated 在输入时绝对不会触发
onUpdated(() => {
  console.log("%c[Warning] 父组件触发了重绘!", "color: orange");
});
</script>

AtomicInput .vue

javascript 复制代码
<template>
  <div class="field-item">
    <label>{{ name }}: </label>
    <input :value="localValue" @input="onInput" />
    <span v-if="renderTime > 0" style="color: blue; margin-left: 10px">
      耗时: {{ renderTime }}ms
    </span>
  </div>
</template>

<script setup>
import { inject, computed, ref, onUpdated } from "vue";

const props = defineProps(["name"]);
const { state, update } = inject("formContext");

const localValue = computed(() => state[props.name]);
const renderTime = ref(0);
let startTime = 0;

const onInput = (e) => {
  startTime = performance.now();
  update(props.name, e.target.value);
};

onUpdated(() => {
  if (startTime > 0) {
    renderTime.value = (performance.now() - startTime).toFixed(2);
    console.log(
      `%c[Atomic Result] 字段 ${props.name} 更新耗时: ${renderTime.value}ms`,
      "color: green",
    );
    startTime = 0;
  }
});
</script>

此时我们发现patch耗时仅仅只有几毫秒,且父组件没有触发onUpdated

二、原因

为什么会差距这么大呢

其实很简单:vue的更新是组件级别的更新,父组件并没有直接使用formData这个响应式数据,那么formData的单个数据变化不会影响这个父组件;
只要父组件使用到了这个formData,那么就会触发父组件的render

这句话什么意思呢?

我们看下面的代码

javascript 复制代码
<template>
  <div style="padding: 20px">
    <h2>10,000 字段【原子化优化】实验</h2>
    <p>请输入内容,你会发现父组件<b>完全不触发</b>更新:</p>

    <div style="margin-bottom: 20px; color: green; font-weight: bold">
      父组件状态:静默(不参与渲染比对)
    </div>
    {{ formData }}
    <div v-for="key in keys" :key="key" style="margin-bottom: 5px">
      <AtomicInput :name="key" />
    </div>
  </div>
</template>

<script setup>
import { reactive, provide, onUpdated, ref } from "vue";
import AtomicInput from "./AtomicInput.vue"; // 引入下面的子组件
const a = ref(0);
const keys = Array.from({ length: 10000 }, (_, i) => `field_${i}`);
const formData = reactive(Object.fromEntries(keys.map((k) => [k, ""])));

// 1. 提供数据源和更新方法
provide("formContext", {
  state: formData,
  update: (key, val) => {
    formData[key] = val;
    handleChange();
  },
});
const handleChange = () => {
  console.log(formData);
};
// 2. 这里的 onUpdated 在输入时绝对不会触发
onUpdated(() => {
  console.log("%c[Warning] 父组件触发了重绘!", "color: orange");
});
</script>

与上面的唯一区别就是父组件渲染了formData

此时我们输入内容

就会发现,父组件发生了render,耗时也变长了

javascript 复制代码
<template>
  <div style="padding: 20px">
    <h2>10,000 字段【原子化优化】实验</h2>
    <p>请输入内容,你会发现父组件<b>完全不触发</b>更新:</p>

    <div style="margin-bottom: 20px; color: green; font-weight: bold">
      父组件状态:静默(不参与渲染比对)
    </div>
    <!-- {{ formData }} -->
    <div v-for="key in keys" :key="key" style="margin-bottom: 5px">
      <AtomicInput :name="key" :value="formData[key]" />
    </div>
  </div>
</template>

<script setup>
import { reactive, provide, onUpdated, ref } from "vue";
import AtomicInput from "./AtomicInput.vue"; // 引入下面的子组件
const a = ref(0);
const keys = Array.from({ length: 10000 }, (_, i) => `field_${i}`);
const formData = reactive(Object.fromEntries(keys.map((k) => [k, ""])));

// 1. 提供数据源和更新方法
provide("formContext", {
  state: formData,
  update: (key, val) => {
    formData[key] = val;
    handleChange();
  },
});
const handleChange = () => {
  console.log(formData);
};
// 2. 这里的 onUpdated 在输入时绝对不会触发
onUpdated(() => {
  console.log("%c[Warning] 父组件触发了重绘!", "color: orange");
});
</script>

当把formData的某个值传给子组件呢

也触发了父组件的render,所以这就是上面的代码为什么用provide这种方式,就是为了避免在父组件的template中使用到这个响应式数据

总结

当然,也能看得出来其实哪怕数据量大,其实渲染时长也并不算特别大,但是如果单个组件过于复杂的话,可能会导致渲染卡顿

tips:以上代码由ai生成

相关推荐
打瞌睡的朱尤2 小时前
蓝桥杯复习大纲
前端·javascript·vue.js
许彰午2 小时前
# Excel转PDF合并单元格边框错乱?jxl+iText逐格解析样式,政务报表精准还原方案
前端·javascript·pdf
zmsculpture2 小时前
现代玻璃钢雕塑制作工艺
性能优化·雕塑制作过程·玻璃钢雕塑
观无2 小时前
html+nginx实现看板
前端·nginx·html
bcbobo21cn2 小时前
Web 3D 正方体贴图
前端·3d·贴图·mesh
聊聊MES那点事2 小时前
报表控件Stimulsoft Reports.NET使用教程:发票报告设计
前端·javascript·html·报表工具
小雨青年2 小时前
GitHub Actions 工作流性能优化实战
性能优化·github
予你@。2 小时前
Vue2 使用 html2canvas 将 HTML 生成图片并上传到服务器
前端·html
星晨雪海2 小时前
优惠券秒杀的核心业务逻辑
java·前端·数据库