本文将为你带来 10 个经过实战检验的 Vue 3 性能优化技巧!
shallowReactive
代替 reactive
—— 节能模式reactive
默认使用深度响应式,这在处理大型数据集时会严重影响性能。shallowReactive
!它只追踪对象顶层的属性变更,就像是为响应式系统开启了“省电模式”。这对于复杂的对象来说是完美的解决方案。import { shallowReactive } from "vue";
// 使用 shallowReactive 创建一个仅顶层属性为响应式的对象
const userInfo = shallowReactive({
name: "前端专家",
address: { city: "", street: "" }, // 嵌套对象
hobbies: ["编程", "调试"],
});
// ✅ 顶层属性的变更会触发更新
userInfo.name = "Vue 高手";
// ❌ 嵌套对象属性的变更不会触发更新,从而避免了不必要的渲染
userInfo.address.city = "旧金山";
shallowReactive
能大幅减少不必要的重渲染。这是 Vue 3 优化中 必备 的技巧。toRefs
实现 Ref 解构魔法const name = state.name
)toRefs
能立即将一个响应式对象的每个属性都转换成独立的 ref
。import { reactive, toRefs } from "vue";
// 创建一个响应式对象
const user = reactive({ name: "Jane", age: 30 });
// 使用 toRefs 将对象的属性解构为独立的 ref
const { name, age } = toRefs(user);
// ✅ 现在可以像 ref 一样直接修改值,并触发响应式更新
name.value = "Doe";
{{ name }}
而不是 {{ user.name }}
),并遵循 DRY(Don’t Repeat Yourself)原则。这是提升 Vue 3 代码优雅性的 关键。watchEffect
进行智能侦听watch
需要立即执行回调或侦听多个数据源时的繁琐配置感到烦恼吗?watchEffect
会自动追踪其依赖,并在依赖变更后重新运行回调函数。import { ref, watchEffect } from "vue";
const count = ref(0);
const double = ref(0);
// watchEffect 会立即执行一次,然后自动追踪其内部用到的响应式依赖(这里是 `count`)
watchEffect(() => {
// 当 count 的值变化时,这个函数会自动重新执行
double.value = count.value * 2;
});
// 增加 count 的值,上面的 watchEffect 回调将会被触发
count.value++;
Suspense
—— 让异步加载如丝般顺滑<Suspense>
组件优雅地处理加载状态。<template>
<Suspense>
<!-- #default 插槽:当异步组件加载完成时显示 -->
<template #default>
<AsyncComponent />
</template>
<!-- #fallback 插槽:在异步组件加载期间显示的“加载中”UI -->
<template #fallback>
<div>全力加载中...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from "vue";
// 使用 defineAsyncComponent 定义一个异步组件
const AsyncComponent = defineAsyncComponent(() => import("./AsyncComponent.vue"));
</script>
Teleport
—— 随心所欲地渲染!<body>
)吗?<Teleport>
可以将内容“传送”到 DOM 中的任何目标节点。<template>
<button @click="show = true">打开模态框</button>
<!-- 使用 Teleport 将内部内容传送到 body 标签下 -->
<Teleport to="body">
<div
v-if="show"
class="modal"
>
<!-- 这个 div 会被渲染在 <body> 的直接子节点中 -->
这是一个模态框
<button @click="show = false">关闭</button>
</div>
</Teleport>
</template>
z-index
层级问题和 CSS 作用域限制。这是处理 Vue 3 组件布局的 秘密武器。v-copy
指令 —— 一键复制document.execCommand('copy')
的逻辑?// 在 main.js 或插件文件中定义一个全局自定义指令
app.directive("copy", {
// 当指令绑定到元素上时触发
mounted(el, binding) {
el.addEventListener("click", () => {
// 创建一个临时的 textarea 用于执行复制操作
const textarea = document.createElement("textarea");
// binding.value 是指令绑定的值,即 'Text to copy'
textarea.value = binding.value;
document.body.appendChild(textarea);
textarea.select();
document.execCommand("copy");
// 操作完成后移除临时元素
document.body.removeChild(textarea);
alert("已复制!");
});
},
});
<!-- 在组件中直接使用指令 -->
<button v-copy="'需要复制的文本'">点我复制</button>
// 插件:为所有 store 添加一个 $reset 方法
const resetPlugin = ({ store }) => {
// $initialState 是我们自定义添加的属性,用于存储初始状态
const initialState = JSON.parse(JSON.stringify(store.$state));
store.$reset = () => store.$patch(initialState);
};
// 创建 Pinia 实例并使用插件
const pinia = createPinia();
pinia.use(resetPlugin);
// 在组件中使用
const userStore = useUserStore();
userStore.$reset(); // 调用插件添加的自定义方法!
v-memo
—— 为列表渲染涡轮增压v-memo
可以根据依赖项缓存 VNode。<ul>
<li
v-for="item in items"
:key="item.id"
<!-- v-memo 接收一个依赖数组 -->
<!-- 只有当 item.id 或 item.status 变化时,这个 li 才会重新渲染 -->
v-memo="[item.id, item.status]"
>
{{ item.name }} - {{ item.status }}
</li>
</ul>
useIntersectionObserver
—— 智能懒加载<script setup>
import { ref } from 'vue';
import { useIntersectionObserver } from '@vueuse/core';
// 创建一个 ref 来引用目标元素
const target = ref(null);
// 一个 ref 用于追踪元素是否可见
const isVisible = ref(false);
// 设置 Intersection Observer
useIntersectionObserver(
target, // 监听的目标元素
([{ isIntersecting }]) => {
// 当元素进入视口时,isIntersecting 会变为 true
if (isIntersecting) {
isVisible.value = true;
}
},
);
</script>
<template>
<img
ref="target"
<!-- 仅当元素可见时才设置 src 属性,从而实现懒加载 -->
:src="isVisible ? 'real-image-url.jpg' : 'placeholder.png'"
alt="懒加载图片"
>
</template>
// composables/useFormValidation.js
import { ref } from "vue";
export default function useFormValidation() {
// 封装表单数据和错误状态
const email = ref("");
const errors = ref({});
// 封装验证逻辑
const validate = () => {
errors.value = {};
if (!email.value) {
errors.value.email = "此项为必填项";
} else if (!email.value.includes("@")) {
errors.value.email = "请输入有效的邮箱地址";
}
// 如果错误对象没有属性,则验证通过
return Object.keys(errors.value).length === 0;
};
// 返回响应式状态和方法
return { email, errors, validate };
}
<!-- 在组件中使用 -->
<script setup>
import useFormValidation from "./composables/useFormValidation";
// 像使用普通 Hook 一样调用,获取所需的状态和方法
const { email, errors, validate } = useFormValidation();
</script>
<template>
<input
v-model="email"
@blur="validate"
/>
<p v-if="errors.email">{{ errors.email }}</p>
</template>
评论: