Vue3 进阶特性与性能优化
2026/3/20大约 8 分钟
Vue3 进阶特性与性能优化:打造高性能应用
经过基础知识的学习,是时候深入 Vue3 的进阶特性了。本文将探讨 Vue3 的高级用法、响应式原理、TypeScript 集成以及性能优化策略,帮你写出更专业的 Vue3 应用。
Vue3 响应式原理
Proxy vs Object.defineProperty
// Vue2:Object.defineProperty
// 缺点:无法检测属性添加/删除、数组索引变化
// Vue3:Proxy
// 优点:可以代理整个对象,拦截所有操作
const obj = { name: "张三" };
const proxy = new Proxy(obj, {
get(target, key, receiver) {
console.log(`读取 ${key}`);
track(target, key); // 依赖收集
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log(`设置 ${key} = ${value}`);
const result = Reflect.set(target, key, value, receiver);
trigger(target, key); // 触发更新
return result;
},
deleteProperty(target, key) {
console.log(`删除 ${key}`);
const result = Reflect.deleteProperty(target, key);
trigger(target, key);
return result;
},
});
响应式的边界情况
import {
ref,
reactive,
shallowRef,
shallowReactive,
markRaw,
toRaw,
} from "vue";
// 深层响应式(默认)
const state = reactive({
nested: {
deep: {
value: 1,
},
},
});
state.nested.deep.value = 2; // 会触发更新
// 浅层响应式
const shallow = shallowReactive({
nested: {
value: 1,
},
});
shallow.nested.value = 2; // 不会触发更新
shallow.nested = { value: 2 }; // 会触发更新
// shallowRef:只有 .value 是响应式的
const shallowState = shallowRef({ count: 0 });
shallowState.value.count++; // 不会触发更新
shallowState.value = { count: 1 }; // 会触发更新
// markRaw:标记对象永远不会变成响应式
const rawObj = markRaw({ data: "raw" });
const state = reactive({ raw: rawObj });
state.raw.data = "changed"; // 不会触发更新
// toRaw:获取响应式对象的原始对象
const original = { count: 0 };
const proxy = reactive(original);
console.log(toRaw(proxy) === original); // true
响应式调试
import {
watchEffect,
watchPostEffect,
onRenderTracked,
onRenderTriggered,
} from "vue";
// watchEffect 调试
watchEffect(
() => {
// ...
},
{
onTrack(e) {
// 当响应式属性被追踪时调用
console.log("tracked:", e);
},
onTrigger(e) {
// 当响应式属性触发更新时调用
console.log("triggered:", e);
debugger; // 可以在这里打断点
},
}
);
// 组件渲染调试
onRenderTracked((e) => {
console.log("render tracked:", e);
});
onRenderTriggered((e) => {
console.log("render triggered:", e);
});
TypeScript 深度集成
组件类型定义
<script setup lang="ts">
import { ref, computed, PropType } from "vue";
// 接口定义
interface User {
id: number;
name: string;
email: string;
role: "admin" | "user" | "guest";
}
// Props 类型
const props = defineProps<{
user: User;
loading?: boolean;
onSuccess?: (data: User) => void;
}>();
// 带默认值
const props = withDefaults(
defineProps<{
user: User;
loading?: boolean;
size?: "sm" | "md" | "lg";
}>(),
{
loading: false,
size: "md",
}
);
// Emits 类型
const emit = defineEmits<{
(e: "update", user: User): void;
(e: "delete", id: number): void;
(e: "change", value: string, oldValue: string): void;
}>();
// Vue 3.3+ 简化语法
const emit = defineEmits<{
update: [user: User];
delete: [id: number];
change: [value: string, oldValue: string];
}>();
// ref 类型
const count = ref<number>(0);
const user = ref<User | null>(null);
const list = ref<User[]>([]);
// computed 类型(自动推断)
const doubleCount = computed(() => count.value * 2);
// 显式类型
const fullName = computed<string>(() => {
return `${props.user.name}`;
});
</script>
组件实例类型
<script setup lang="ts">
import { ref } from "vue";
import ChildComponent from "./ChildComponent.vue";
// 获取组件实例类型
const childRef = ref<InstanceType<typeof ChildComponent> | null>(null);
function callChild() {
childRef.value?.someMethod();
}
</script>
<template>
<ChildComponent ref="childRef" />
</template>
全局类型声明
// types/vue.d.ts
import { ComponentCustomProperties } from "vue";
import { Store } from "vuex";
import { Router } from "vue-router";
declare module "@vue/runtime-core" {
// 全局属性
interface ComponentCustomProperties {
$api: typeof api;
$filters: typeof filters;
$store: Store<State>;
$router: Router;
}
// 全局组件
interface GlobalComponents {
BaseButton: typeof import("./components/BaseButton.vue")["default"];
BaseInput: typeof import("./components/BaseInput.vue")["default"];
}
}
export {};
组合式函数类型
// composables/useCounter.ts
import { ref, computed, Ref } from "vue";
interface UseCounterOptions {
min?: number;
max?: number;
step?: number;
}
interface UseCounterReturn {
count: Ref<number>;
doubleCount: Readonly<Ref<number>>;
increment: () => void;
decrement: () => void;
reset: () => void;
set: (value: number) => void;
}
export function useCounter(
initialValue: number = 0,
options: UseCounterOptions = {}
): UseCounterReturn {
const { min = -Infinity, max = Infinity, step = 1 } = options;
const count = ref(initialValue);
const doubleCount = computed(() => count.value * 2);
function increment() {
const newValue = count.value + step;
if (newValue <= max) count.value = newValue;
}
function decrement() {
const newValue = count.value - step;
if (newValue >= min) count.value = newValue;
}
function reset() {
count.value = initialValue;
}
function set(value: number) {
if (value >= min && value <= max) {
count.value = value;
}
}
return {
count,
doubleCount,
increment,
decrement,
reset,
set,
};
}
内置组件详解
Teleport
<template>
<button @click="showModal = true">打开弹窗</button>
<!-- 传送到 body -->
<Teleport to="body">
<div v-if="showModal" class="modal">
<h2>弹窗标题</h2>
<p>弹窗内容...</p>
<button @click="showModal = false">关闭</button>
</div>
</Teleport>
<!-- 条件禁用 -->
<Teleport to="body" :disabled="isMobile">
<div class="tooltip">...</div>
</Teleport>
<!-- 传送到指定元素 -->
<Teleport to="#modals">
<Modal />
</Teleport>
</template>
<script setup>
import { ref } from "vue";
const showModal = ref(false);
const isMobile = ref(false);
</script>
Suspense
<template>
<Suspense>
<!-- 默认插槽:异步内容 -->
<template #default>
<AsyncComponent />
</template>
<!-- fallback 插槽:加载状态 -->
<template #fallback>
<div class="loading">
<Spinner />
<p>加载中...</p>
</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from "vue";
// 异步组件
const AsyncComponent = defineAsyncComponent(() =>
import("./HeavyComponent.vue")
);
</script>
<!-- 支持 async setup 的组件 -->
<script setup>
// AsyncUserProfile.vue
const user = await fetchUser(); // 顶层 await
</script>
Transition 与 TransitionGroup
<template>
<!-- 单元素过渡 -->
<Transition name="fade">
<p v-if="show">Hello</p>
</Transition>
<!-- 使用 CSS 类名 -->
<Transition
enter-active-class="animate__animated animate__fadeIn"
leave-active-class="animate__animated animate__fadeOut"
>
<p v-if="show">Hello</p>
</Transition>
<!-- JavaScript 钩子 -->
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
:css="false"
>
<p v-if="show">Hello</p>
</Transition>
<!-- 列表过渡 -->
<TransitionGroup name="list" tag="ul">
<li v-for="item in items" :key="item.id">
{{ item.text }}
</li>
</TransitionGroup>
</template>
<script setup>
import { ref } from "vue";
import gsap from "gsap";
const show = ref(true);
function onEnter(el, done) {
gsap.to(el, {
opacity: 1,
duration: 0.5,
onComplete: done,
});
}
function onLeave(el, done) {
gsap.to(el, {
opacity: 0,
duration: 0.5,
onComplete: done,
});
}
</script>
<style>
/* 过渡类名 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/* 列表过渡 */
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
/* 移动动画 */
.list-move {
transition: transform 0.5s ease;
}
</style>
KeepAlive
<template>
<!-- 基础用法 -->
<KeepAlive>
<component :is="currentComponent" />
</KeepAlive>
<!-- include/exclude -->
<KeepAlive :include="['ComponentA', 'ComponentB']">
<component :is="currentComponent" />
</KeepAlive>
<KeepAlive :exclude="/^Tab/">
<component :is="currentComponent" />
</KeepAlive>
<!-- 最大缓存数 -->
<KeepAlive :max="10">
<router-view />
</KeepAlive>
</template>
<script setup>
import { onActivated, onDeactivated } from "vue";
// 组件被激活时
onActivated(() => {
console.log("activated");
fetchLatestData();
});
// 组件被停用时
onDeactivated(() => {
console.log("deactivated");
pauseTimer();
});
</script>
自定义指令(Vue3 版)
指令钩子
// Vue3 指令钩子(与组件生命周期对齐)
const myDirective = {
// 绑定元素的 attribute 或事件监听器被应用之前
created(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件挂载之前
beforeMount(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件挂载之后
mounted(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件更新之前
beforeUpdate(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件更新之后
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载之前
beforeUnmount(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载之后
unmounted(el, binding, vnode, prevVnode) {},
};
实用指令示例
// 权限指令
const vPermission = {
mounted(el, binding) {
const permission = binding.value;
const userPermissions = useUserStore().permissions;
if (!userPermissions.includes(permission)) {
el.parentNode?.removeChild(el);
}
},
};
// 点击外部关闭
const vClickOutside = {
mounted(el, binding) {
el._clickOutside = (event) => {
if (!el.contains(event.target) && el !== event.target) {
binding.value(event);
}
};
document.addEventListener("click", el._clickOutside);
},
unmounted(el) {
document.removeEventListener("click", el._clickOutside);
},
};
// 防抖
const vDebounce = {
mounted(el, binding) {
let timer = null;
el.addEventListener("input", () => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
binding.value();
}, binding.arg || 300);
});
},
};
// 复制到剪贴板
const vCopy = {
mounted(el, binding) {
el._copyHandler = async () => {
try {
await navigator.clipboard.writeText(binding.value);
// 可以触发提示
} catch (err) {
console.error("复制失败:", err);
}
};
el.addEventListener("click", el._copyHandler);
},
updated(el, binding) {
// 更新复制内容
},
unmounted(el) {
el.removeEventListener("click", el._copyHandler);
},
};
// 注册
app.directive("permission", vPermission);
app.directive("click-outside", vClickOutside);
app.directive("debounce", vDebounce);
app.directive("copy", vCopy);
性能优化
1. 组件懒加载
import { defineAsyncComponent } from "vue";
// 基础异步组件
const AsyncModal = defineAsyncComponent(() => import("./components/Modal.vue"));
// 带配置的异步组件
const AsyncComponent = defineAsyncComponent({
loader: () => import("./HeavyComponent.vue"),
loadingComponent: LoadingSpinner,
errorComponent: ErrorDisplay,
delay: 200,
timeout: 3000,
});
// 路由懒加载
const routes = [
{
path: "/dashboard",
component: () => import("./views/Dashboard.vue"),
},
];
2. 虚拟列表
<template>
<!-- 使用 vue-virtual-scroller -->
<RecycleScroller
:items="items"
:item-size="50"
key-field="id"
v-slot="{ item }"
>
<div class="item">{{ item.name }}</div>
</RecycleScroller>
</template>
<script setup>
import { RecycleScroller } from "vue-virtual-scroller";
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
</script>
3. 大数据优化
import { shallowRef, triggerRef, markRaw } from "vue";
// 大型静态数据使用 shallowRef
const bigData = shallowRef(generateBigData());
// 手动触发更新
function updateData() {
bigData.value[0].name = "updated";
triggerRef(bigData); // 手动触发
}
// 不需要响应式的数据使用 markRaw
const staticConfig = markRaw({
// 大量配置数据
});
// 或直接 Object.freeze
const frozenData = Object.freeze(bigArray);
4. 计算属性缓存
// ✅ 使用 computed 缓存
const filteredList = computed(() => {
return list.value.filter((item) => item.active);
});
// ❌ 避免在模板中使用方法进行复杂计算
// <div v-for="item in filterList()">
5. v-once 和 v-memo
<template>
<!-- v-once:只渲染一次 -->
<div v-once>
<h1>{{ staticTitle }}</h1>
<p>这个内容永远不会更新</p>
</div>
<!-- v-memo:条件缓存(Vue 3.2+) -->
<div v-for="item in list" :key="item.id" v-memo="[item.selected]">
<!-- 只有 item.selected 变化时才重新渲染 -->
<HeavyComponent :data="item" />
</div>
</template>
6. 事件处理优化
<template>
<!-- 使用事件委托 -->
<ul @click="handleClick">
<li v-for="item in items" :key="item.id" :data-id="item.id">
{{ item.name }}
</li>
</ul>
</template>
<script setup>
function handleClick(e) {
const id = e.target.dataset.id;
if (id) {
// 处理点击
}
}
</script>
7. 组件缓存策略
<template>
<router-view v-slot="{ Component }">
<KeepAlive :include="cachedViews" :max="10">
<component :is="Component" :key="$route.fullPath" />
</KeepAlive>
</router-view>
</template>
<script setup>
import { computed } from "vue";
import { useAppStore } from "@/stores/app";
const appStore = useAppStore();
const cachedViews = computed(() => appStore.cachedViews);
</script>
8. 打包优化
// vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [vue()],
build: {
// 分包策略
rollupOptions: {
output: {
manualChunks: {
"vue-vendor": ["vue", "vue-router", "pinia"],
"ui-vendor": ["element-plus"],
utils: ["lodash-es", "dayjs"],
},
},
},
// 压缩
minify: "terser",
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
},
});
错误处理
// 全局错误处理
app.config.errorHandler = (err, instance, info) => {
console.error("Vue Error:", err);
console.error("Component:", instance);
console.error("Error Info:", info);
// 上报到监控系统
reportError(err, { component: instance?.$options?.name, info });
};
// 全局警告处理(开发环境)
app.config.warnHandler = (msg, instance, trace) => {
console.warn("Vue Warning:", msg);
};
// 组件级错误边界
const errorCaptured = (err, instance, info) => {
// 处理错误
return false; // 阻止错误继续传播
};
<!-- ErrorBoundary.vue -->
<template>
<slot v-if="!error" />
<div v-else class="error-boundary">
<h2>出错了</h2>
<p>{{ error.message }}</p>
<button @click="reset">重试</button>
</div>
</template>
<script setup>
import { ref, onErrorCaptured } from "vue";
const error = ref(null);
onErrorCaptured((err) => {
error.value = err;
return false;
});
function reset() {
error.value = null;
}
</script>
调试技巧
Vue Devtools
// 在代码中标记组件名称(用于 Devtools)
defineOptions({
name: "MyComponent",
});
// 或在非 setup 语法中
export default {
name: "MyComponent",
};
性能分析
// 开启性能追踪
app.config.performance = true;
// 在 Chrome DevTools 的 Performance 面板可以看到:
// - Component render
// - Component patch
// - Component setup
总结
Vue3 的进阶特性让我们能够构建更高效、更可维护的应用:
- 响应式原理:Proxy 带来更强大的响应式能力
- TypeScript:一流的类型支持,更安全的开发体验
- 内置组件:Teleport、Suspense、Transition 等
- 自定义指令:与组件生命周期对齐的钩子
- 性能优化:多种优化手段提升应用性能
掌握这些进阶知识,你就能在 Vue3 项目中游刃有余。接下来的文章将介绍 Vue 生态中的重要工具:Vue Router 和状态管理。