1194 字
6 分钟
Vue 3 组合式 API 深度实战:从响应式原理到工程化实践
Vue 3 组合式 API 深度实战:从响应式原理到工程化实践
🎯 前言:Vue 3 带来了组合式 API(Composition API),这是一个全新的代码组织方式,让我们能够更好地复用逻辑、组织代码。这篇文章将深入讲解 Vue 3 的核心概念和实战技巧。
一、响应式系统原理
1.1 Proxy vs Object.defineProperty
Vue 3 使用 Proxy 替代了 Vue 2 的 Object.defineProperty,这是一个重大的架构改进:
// Vue 2 的方式 - 无法检测新增属性Object.defineProperty(obj, 'key', { get() { return value }, set(newVal) { value = newVal }});
// Vue 3 的方式 - Proxy 可以代理整个对象const proxy = new Proxy(target, { get(target, key) { return target[key] }, set(target, key, value) { target[key] = value; return true }});Proxy 的优势:
- 可以监听动态新增的属性
- 可以监听数组索引和长度变化
- 可以监听 Map、Set 等集合类型
- 性能更好,内存占用更少
1.2 ref 和 reactive
import { ref, reactive } from 'vue';
// ref:用于基本类型const count = ref(0);console.log(count.value); // 访问值需要 .value
// reactive:用于对象const state = reactive({ name: 'Vue', version: 3});console.log(state.name); // 直接访问属性
// 最佳实践const user = reactive({ profile: ref({ name: '张三', age: 25 })});// 访问:user.profile.name(不需要 .value,因为 reactive 会解包)1.3 computed 和 watch
import { ref, computed, watch, watchEffect } from 'vue';
// computedconst firstName = ref('张');const lastName = ref('三');const fullName = computed(() => firstName.value + lastName.value);
// 可写的 computedconst fullNameWritable = computed({ get: () => firstName.value + lastName.value, set: (val) => { [firstName.value, lastName.value] = val.split(' '); }});
// watch - 懒执行,可以访问新旧值watch(count, (newVal, oldVal) => { console.log('count changed:', oldVal, '->', newVal);});
// watchEffect - 立即执行,自动追踪依赖watchEffect(() => { console.log('count is:', count.value);});二、组合式 API 实战
2.1 setup 函数
<script setup>import { ref, onMounted } from 'vue';
// 直接声明响应式数据const count = ref(0);const message = ref('Hello Vue 3');
// 方法const increment = () => { count.value++;};
// 生命周期钩子onMounted(() => { console.log('Component mounted');});</script>
<template> <div> <p>{{ message }}</p> <p>Count: {{ count }}</p> <button @click="increment">+1</button> </div></template>2.2 逻辑复用 - Composable
import { ref, onMounted, onUnmounted } from 'vue';
export function useMouse() { const x = ref(0); const y = ref(0);
function update(event: MouseEvent) { x.value = event.pageX; y.value = event.pageY; }
onMounted(() => window.addEventListener('mousemove', update)); onUnmounted(() => window.removeEventListener('mousemove', update));
return { x, y };}
// 使用<script setup>import { useMouse } from './useMouse';const { x, y } = useMouse();</script>2.3 生命周期钩子对照
| Options API | Composition API |
|---|---|
| beforeCreate | 不需要,直接在 setup 中执行 |
| created | 不需要,直接在 setup 中执行 |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeUnmount | onBeforeUnmount |
| unmounted | onUnmounted |
| errorCaptured | onErrorCaptured |
| renderTracked | onRenderTracked |
| renderTriggered | onRenderTriggered |
三、性能优化
3.1 组件懒加载
// 异步组件import { defineAsyncComponent } from 'vue';
const AsyncComp = defineAsyncComponent(() => import('./components/HeavyComponent.vue'));
// 带加载状态的异步组件const AsyncCompWithLoading = defineAsyncComponent({ loader: () => import('./components/HeavyComponent.vue'), loadingComponent: LoadingComponent, errorComponent: ErrorComponent, delay: 200, timeout: 3000});3.2 v-memo 指令
<template> <div v-memo="[valueA, valueB]"> <!-- 只有当 valueA 或 valueB 变化时才会重新渲染 --> <span>{{ valueA }} - {{ valueB }}</span> </div></template>3.3 虚拟列表
<script setup>import { ref, computed } from 'vue';
const items = ref(Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}`})));
const containerHeight = 500;const itemHeight = 50;const scrollTop = ref(0);
const visibleItems = computed(() => { const start = Math.floor(scrollTop.value / itemHeight); const end = start + Math.ceil(containerHeight / itemHeight); return items.value.slice(start, end).map((item, index) => ({ ...item, index: start + index }));});</script>
<template> <div class="container" :style="{ height: containerHeight + 'px', overflow: 'auto' }" @scroll="e => scrollTop = e.target.scrollTop" > <div :style="{ height: items.length * itemHeight + 'px' }"> <div v-for="item in visibleItems" :key="item.id" :style="{ height: itemHeight + 'px', transform: `translateY(${item.index * itemHeight}px)`, position: 'absolute' }" > {{ item.text }} </div> </div> </div></template>四、TypeScript 集成
4.1 类型声明
// 定义 Props 类型interface Props { title: string; count?: number; items: string[];}
// 使用 defineProps 宏const props = withDefaults(defineProps<Props>(), { count: 0});
// 定义 Emitsinterface Emits { (e: 'update', value: string): void; (e: 'delete', id: number): void;}
const emit = defineEmits<Emits>();4.2 泛型组件
<script setup lang="ts" generic="T extends { id: number }">const props = defineProps<{ items: T[]; selectedId?: number;}>();</script>五、状态管理 Pinia
import { defineStore } from 'pinia';import { ref, computed } from 'vue';
export const useUserStore = defineStore('user', () => { // State const user = ref<User | null>(null); const isLoggedIn = ref(false);
// Getters const fullName = computed(() => { if (!user.value) return ''; return `${user.value.firstName} ${user.value.lastName}`; });
// Actions async function login(credentials: Credentials) { const response = await api.login(credentials); user.value = response.user; isLoggedIn.value = true; }
function logout() { user.value = null; isLoggedIn.value = false; }
return { user, isLoggedIn, fullName, login, logout };});六、路由 Vue Router 4
import { createRouter, createWebHistory } from 'vue-router';import { useUserStore } from '@/stores/user';
const routes = [ { path: '/', name: 'Home', component: () => import('@/views/Home.vue') }, { path: '/dashboard', name: 'Dashboard', component: () => import('@/views/Dashboard.vue'), meta: { requiresAuth: true }, children: [ { path: 'profile', component: () => import('@/views/Profile.vue') } ] }];
const router = createRouter({ history: createWebHistory(), routes});
// 路由守卫router.beforeEach((to, from, next) => { const userStore = useUserStore();
if (to.meta.requiresAuth && !userStore.isLoggedIn) { next('/login'); } else { next(); }});七、总结
Vue 3 带来了许多激动人心的改进:
- 响应式系统:使用 Proxy 替代 Object.defineProperty
- 组合式 API:更灵活的代码组织方式
- TypeScript 支持:更好的类型推断和提示
- 性能优化:更小的包体积,更快的渲染
- 生态工具:Pinia、Vue Router 4 等配套工具
掌握 Vue 3 的这些特性,将帮助你构建更现代化、更高效的 Web 应用!
Vue 3 组合式 API 深度实战:从响应式原理到工程化实践
https://www.oferry.com/posts/a82/