Vue 3 Composition API高级技巧
Vue 3的Composition API为Vue应用开发带来了革命性的变化,它提供了更好的代码组织、类型支持和复用能力。作为一名Vue开发者,掌握Composition API的高级技巧将让你写出更加优雅和高效的代码。
在本文中,我将深入探讨Composition API的高级特性,包括自定义Hooks、复杂状态管理、性能优化等内容,帮助你充分利用Vue 3的强大功能。
1. Composition API基础回顾
1.1 Options API vs Composition API
<!-- Options API --> <template> <div> <p>Count: {{ count }}</p> <button @click="increment">Increment</button> </div> </template>
<script> export default { data() { return { count: 0 } }, methods: { increment() { this.count++; } } } </script>
<!-- Composition API --> <template> <div> <p>Count: {{ count }}</p> <button @click="increment">Increment</button> </div> </template>
<script> import { ref } from 'vue';
export default { setup() { const count = ref(0); const increment = () => { count.value++; }; return { count, increment }; } } </script>
|
1.2 setup()函数详解
<script> import { ref, reactive, computed, watch, onMounted } from 'vue';
export default { setup(props, { emit }) { // 响应式数据 const count = ref(0); const user = reactive({ name: 'John', age: 30 }); // 计算属性 const doubleCount = computed(() => count.value * 2); // 监听器 watch(count, (newVal, oldVal) => { console.log(`Count changed from ${oldVal} to ${newVal}`); }); // 方法 const increment = () => { count.value++; }; const updateUser = (newName) => { user.name = newName; }; // 生命周期钩子 onMounted(() => { console.log('Component mounted'); }); // 返回需要模板访问的响应式数据和函数 return { count, doubleCount, user, increment, updateUser }; } } </script>
|
1.3 语法糖:<script setup>
<script setup> import { ref, computed, watch, onMounted } from 'vue';
// 直接定义响应式数据 const count = ref(0); const user = reactive({ name: 'John', age: 30 });
// 直接定义计算属性 const doubleCount = computed(() => count.value * 2);
// 直接定义方法 const increment = () => { count.value++; };
// 直接定义监听器 watch(count, (newVal, oldVal) => { console.log(`Count changed from ${oldVal} to ${newVal}`); });
// 直接使用生命周期钩子 onMounted(() => { console.log('Component mounted'); }); </script>
|
2. 响应式系统深入理解
2.1 ref vs reactive
<script setup> import { ref, reactive } from 'vue';
// ref - 用于基础类型对象 const count = ref(0); // 访问时需要 .value const message = ref('Hello Vue');
// reactive - 用于对象 const user = reactive({ name: 'John', age: 30 });
// ref 可以保持引用 const objectRef = ref({ count: 0, user: { name: 'John' } });
// reactive 的深层响应式特性 const deepObject = reactive({ nested: { value: 'deep' } }); </script>
|
2.2 响应式解构
<script setup> import { toRefs } from 'vue';
const state = reactive({ count: 0, name: 'Vue', user: { firstName: 'John', lastName: 'Doe' } });
// toRefs 保持响应性 const { count, name, user } = toRefs(state);
// 直接解构会失去响应性 // const { count, name } = state; // 错误!
// 使用 reactive 的解构 const userCopy = reactive(state.user); // 深拷贝 </script>
|
2.3 自定义响应式函数
<script setup> import { ref, reactive, customRef } from 'vue';
// 自定义ref function useDebouncedRef(value, delay = 300) { let timeout; const debouncedValue = customRef((track, trigger) => { return { get() { track(); // 跟踪依赖 return value; }, set(newValue) { clearTimeout(timeout); timeout = setTimeout(() => { value = newValue; trigger(); // 触发更新 }, delay); } }; }); return debouncedValue; }
// 使用自定义ref const debouncedInput = useDebouncedRef(''); </script>
|
3. 高级组合函数
3.1 自定义Hooks模式
<!-- hooks/useCounter.js --> import { ref, computed } from 'vue';
export function useCounter(initialValue = 0) { const count = ref(initialValue); const increment = () => { count.value++; }; const decrement = () => { count.value--; }; const reset = () => { count.value = initialValue; }; const doubleCount = computed(() => count.value * 2); return { count, doubleCount, increment, decrement, reset }; }
<!-- 组件中使用 --> <template> <div> <p>Count: {{ count }}</p> <p>Double: {{ doubleCount }}</p> <button @click="increment">+1</button> <button @click="decrement">-1</button> <button @click="reset">Reset</button> </div> </template>
<script setup> import { useCounter } from '@/hooks/useCounter';
const { count, doubleCount, increment, decrement, reset } = useCounter(10); </script>
|
3.2 复杂状态管理Hook
<!-- hooks/useAsyncData.js --> import { ref, computed } from 'vue';
export function useAsyncData(fetchFn, options = {}) { const data = ref(null); const loading = ref(false); const error = ref(null); const { immediate = true, onSuccess, onError } = options; const execute = async () => { loading.value = true; error.value = null; try { data.value = await fetchFn(); if (onSuccess) onSuccess(data.value); } catch (err) { error.value = err; if (onError) onError(err); throw err; } finally { loading.value = false; } }; const refresh = async () => { await execute(); }; const stale = computed(() => { return data.value === null || !loading.value; }); if (immediate) { execute(); } return { data, loading, error, execute, refresh, stale }; }
<!-- 使用示例 --> <template> <div> <div v-if="loading">Loading...</div> <div v-else-if="error" class="error">Error: {{ error.message }}</div> <div v-else> <h2>{{ data.title }}</h2> <p>{{ data.content }}</p> </div> <button @click="refresh">Refresh</button> </div> </template>
<script setup> import { useAsyncData } from '@/hooks/useAsyncData';
const { data, loading, error, refresh } = useAsyncData(async () => { const response = await fetch('https://api.example.com/posts/1'); return response.json(); }); </script>
|
3.3 本地存储Hook
<!-- hooks/useStorage.js --> import { ref, watch } from 'vue';
export function useStorage(key, defaultValue, storage = localStorage) { const storedValue = ref(defaultValue); // 从存储中初始化 try { const item = storage.getItem(key); if (item) { storedValue.value = JSON.parse(item); } } catch (error) { console.error(`Error parsing stored value for key "${key}":`, error); } // 监听值变化并更新存储 watch(storedValue, (newValue) => { try { storage.setItem(key, JSON.stringify(newValue)); } catch (error) { console.error(`Error saving value for key "${key}":`, error); } }, { deep: true }); return storedValue; }
<!-- 使用示例 --> <template> <div> <input v-model="theme" @change="saveTheme" placeholder="Enter theme" /> <p>Current theme: {{ theme }}</p> </div> </template>
<script setup> import { useStorage } from '@/hooks/useStorage';
const theme = useStorage('app-theme', 'light'); </script>
|
4. 组件通信与状态管理
4.1 组件间的高级通信
<!-- 使用provide/inject --> <template> <div class="parent"> <h2>Parent Component</h2> <input v-model="parentMessage" placeholder="Enter message" /> <ChildComponent /> </div> </template>
<script setup> import { ref, provide } from 'vue'; import ChildComponent from './ChildComponent.vue';
const parentMessage = ref('Hello from parent');
// 提供响应式数据和方法 provide('message', parentMessage); provide('updateMessage', (newMessage) => { parentMessage.value = newMessage; }); </script>
<!-- ChildComponent.vue --> <template> <div class="child"> <h3>Child Component</h3> <p>Message: {{ message }}</p> <button @click="updateMessage('Updated from child')"> Update Message </button> </div> </template>
<script setup> import { inject } from 'vue';
const message = inject('message'); const updateMessage = inject('updateMessage'); </script>
|
4.2 状态管理模式
<!-- stores/useAuthStore.js --> import { defineStore } from 'pinia';
export const useAuthStore = defineStore('auth', { state: () => ({ user: null, token: null, isAuthenticated: false }), getters: { userName: (state) => state.user?.name, isLoggedIn: (state) => state.isAuthenticated, hasPermission: (state) => (permission) => { return state.user?.permissions?.includes(permission); } }, actions: { async login(credentials) { try { const response = await fetch('/api/login', { method: 'POST', body: JSON.stringify(credentials) }); const { user, token } = await response.json(); this.user = user; this.token = token; this.isAuthenticated = true; localStorage.setItem('token', token); } catch (error) { throw new Error('Login failed'); } }, logout() { this.user = null; this.token = null; this.isAuthenticated = false; localStorage.removeItem('token'); }, async fetchUserProfile() { const token = localStorage.getItem('token'); if (token) { const response = await fetch('/api/profile', { headers: { 'Authorization': `Bearer ${token}` } }); this.user = await response.json(); } } } });
|
4.3 全局状态管理
<!-- App.vue --> <template> <div id="app"> <Header /> <main> <router-view /> </main> <Footer /> </div> </template>
<script setup> import { provide } from 'vue'; import { useAuthStore } from '@/stores/auth';
const authStore = useAuthStore();
// 提供全局状态 provide('authStore', authStore);
// 应用启动时初始化 provide('initializeApp', async () => { await authStore.fetchUserProfile(); }); </script>
<!-- 组件中使用全局状态 --> <template> <div class="protected-page"> <div v-if="authStore.isLoggedIn"> <h1>Welcome, {{ authStore.userName }}</h1> <button @click="authStore.logout">Logout</button> </div> <div v-else> <p>Please log in to access this page</p> <router-link to="/login">Login</router-link> </div> </div> </template>
<script setup> import { inject } from 'vue'; import { useAuthStore } from '@/stores/auth';
const authStore = useAuthStore(); </script>
|
5. 性能优化技巧
5.1 组件性能优化
<script setup> import { ref, computed, shallowRef } from 'vue';
// 使用shallowRef优化大对象 const largeData = shallowRef({ items: Array(10000).fill(0).map((_, i) => ({ id: i, name: `Item ${i}` })) });
// 使用computed缓存复杂计算 const filteredItems = computed(() => { return largeData.value.items.filter(item => item.name.includes('important')); });
// 使用v-once静态化内容 const staticContent = ref('This content never changes'); </script>
<template> <div> <!-- 使用v-memo缓存模板 --> <div v-for="item in filteredItems" :key="item.id" v-memo="[item.id]"> <span>{{ item.name }}</span> </div> <!-- 使用v-once --> <div v-once>{{ staticContent }}</div> </div> </template>
|
5.2 虚拟列表优化
<!-- components/VirtualList.vue --> <template> <div class="virtual-list" :style="{ height: `${height}px` }" @scroll="handleScroll"> <div class="scroll-content" :style="{ height: `${totalHeight}px` }"> <div class="item" v-for="item in visibleItems" :key="item.id" :style="{ position: 'absolute', top: `${item.offset}px`, height: `${itemHeight}px` }" > {{ item.content }} </div> </div> </div> </template>
<script setup> import { ref, computed, onMounted, onUnmounted } from 'vue';
const props = defineProps({ items: Array, itemHeight: { type: Number, default: 50 }, height: { type: Number, default: 400 } });
const scrollTop = ref(0); const listRef = ref(null);
const startIndex = computed(() => { return Math.floor(scrollTop.value / props.itemHeight); });
const endIndex = computed(() => { return Math.min( props.items.length - 1, Math.ceil((scrollTop.value + props.height) / props.itemHeight) ); });
const visibleItems = computed(() => { const items = []; for (let i = startIndex.value; i <= endIndex.value; i++) { items.push({ id: props.items[i].id, content: props.items[i].content, offset: i * props.itemHeight }); } return items; });
const totalHeight = computed(() => { return props.items.length * props.itemHeight; });
const handleScroll = () => { scrollTop.value = listRef.value.scrollTop; };
onMounted(() => { // 可以在这里预加载可视区域外的项目 });
onUnmounted(() => { // 清理工作 }); </script>
|
5.3 懒加载组件
<template> <div class="app"> <button @click="showComponent = true">Show Component</button> <Suspense v-if="showComponent"> <template #default> <LazyComponent /> </template> <template #fallback> <div>Loading...</div> </template> </Suspense> </div> </template>
<script setup> import { ref, defineAsyncComponent } from 'vue';
const showComponent = ref(false);
const LazyComponent = defineAsyncComponent(() => import('./LazyComponent.vue') ); </script>
|
6. 高级组件模式
6.1 可复用组件设计
<!-- components/BaseTable.vue --> <template> <div class="base-table"> <table> <thead> <tr> <th v-for="column in columns" :key="column.key" @click="column.sortable && sort(column.key)" > {{ column.label }} <span v-if="column.sortable && sortKey === column.key"> {{ sortOrder === 'asc' ? '↑' : '↓' }} </span> </th> </tr> </thead> <tbody> <tr v-for="item in sortedData" :key="item.id"> <td v-for="column in columns" :key="column.key"> <slot :name="`cell-${column.key}`" :value="item[column.key]" :item="item" > {{ item[column.key] }} </slot> </td> </tr> </tbody> </table> </div> </template>
<script setup> import { computed } from 'vue';
const props = defineProps({ data: Array, columns: Array, sortable: { type: Boolean, default: false } });
const sortKey = ref(null); const sortOrder = ref('asc');
const sortedData = computed(() => { if (!sortKey.value) return props.data; return [...props.data].sort((a, b) => { const aVal = a[sortKey.value]; const bVal = b[sortKey.value]; if (aVal < bVal) return sortOrder.value === 'asc' ? -1 : 1; if (aVal > bVal) return sortOrder.value === 'asc' ? 1 : -1; return 0; }); });
const sort = (key) => { if (sortKey.value === key) { sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'; } else { sortKey.value = key; sortOrder.value = 'asc'; } }; </script>
<!-- 使用示例 --> <template> <BaseTable :data="users" :columns="columns"> <template #cell-name="{ value }"> <strong>{{ value }}</strong> </template> <template #cell-age="{ value }"> <span class="age-badge">{{ value }}</span> </template> </BaseTable> </template>
<script setup> import { ref } from 'vue';
const users = ref([ { id: 1, name: 'John', age: 25 }, { id: 2, name: 'Jane', age: 30 }, { id: 3, name: 'Bob', age: 28 } ]);
const columns = [ { key: 'name', label: 'Name', sortable: true }, { key: 'age', label: 'Age', sortable: true } ]; </script>
|
6.2 条件渲染优化
<template> <div class="conditional-render"> <!-- 使用v-if vs v-show --> <div v-if="isVisible" class="heavy-content"> <!-- 重型内容 --> <HeavyComponent /> </div> <!-- 使用v-show频繁切换的场景 --> <div v-show="isToggleable" class="toggleable-content"> <!-- 经常切换的内容 --> </div> <!-- 使用Teleport --> <Teleport to="body"> <div v-if="showModal" class="modal"> Modal content </div> </Teleport> </div> </template>
<script setup> import { ref } from 'vue';
const isVisible = ref(false); const isToggleable = ref(false); const showModal = ref(false); </script>
|
6.3 组件生命周期管理
<script setup> import { onMounted, onUnmounted, onBeforeMount, onBeforeUnmount } from 'vue';
// 组件挂载前 onBeforeMount(() => { console.log('组件即将挂载'); // 可以在这里执行一些准备工作 });
// 组件挂载后 onMounted(() => { console.log('组件已挂载'); // 添加事件监听 const handleResize = () => { console.log('窗口大小改变'); }; window.addEventListener('resize', handleResize); // 清理函数 return () => { window.removeEventListener('resize', handleResize); }; });
// 组件卸载前 onBeforeUnmount(() => { console.log('组件即将卸载'); // 可以在这里执行一些清理工作 });
// 组件卸载后 onUnmounted(() => { console.log('组件已卸载'); // 执行最终清理工作 }); </script>
|
7. 表单处理高级技巧
7.1 复杂表单管理
<template> <form @submit="handleSubmit"> <div class="form-group"> <label>用户名</label> <input v-model="form.username" @blur="validateField('username')" /> <span class="error" v-if="errors.username"> {{ errors.username }} </span> </div> <div class="form-group"> <label>邮箱</label> <input v-model="form.email" @blur="validateField('email')" /> <span class="error" v-if="errors.email"> {{ errors.email }} </span> </div> <div class="form-group"> <label>密码</label> <input type="password" v-model="form.password" @blur="validateField('password')" /> <span class="error" v-if="errors.password"> {{ errors.password }} </span> </div> <button type="submit" :disabled="isSubmitting"> {{ isSubmitting ? '提交中...' : '提交' }} </button> </form> </template>
<script setup> import { ref, reactive, computed } from 'vue';
const form = reactive({ username: '', email: '', password: '' });
const errors = reactive({ username: '', email: '', password: '' });
const isSubmitting = ref(false);
const isValid = computed(() => { return Object.values(form).every(value => value.trim()) && !Object.values(errors).some(error => error); });
const validateField = (field) => { switch (field) { case 'username': if (!form.username.trim()) { errors.username = '用户名不能为空'; } else if (form.username.length < 3) { errors.username = '用户名至少需要3个字符'; } else { errors.username = ''; } break; case 'email': const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!form.email.trim()) { errors.email = '邮箱不能为空'; } else if (!emailRegex.test(form.email)) { errors.email = '请输入有效的邮箱地址'; } else { errors.email = ''; } break; case 'password': if (!form.password) { errors.password = '密码不能为空'; } else if (form.password.length < 6) { errors.password = '密码至少需要6个字符'; } else { errors.password = ''; } break; } };
const validateAll = () => { validateField('username'); validateField('email'); validateField('password'); return isValid.value; };
const handleSubmit = async (e) => { e.preventDefault(); if (!validateAll()) { return; } isSubmitting.value = true; try { const response = await fetch('/api/register', { method: 'POST', body: JSON.stringify(form) }); if (!response.ok) { throw new Error('注册失败'); } // 处理成功 console.log('注册成功'); } catch (error) { console.error('注册错误:', error); alert('注册失败,请重试'); } finally { isSubmitting.value = false; } }; </script>
|
7.2 动态表单字段
<template> <div class="dynamic-form"> <div v-for="(field, index) in dynamicFields" :key="index" class="form-group"> <label>字段名称</label> <input v-model="field.name" placeholder="字段名称" /> <label>字段类型</label> <select v-model="field.type"> <option value="text">文本</option> <option value="number">数字</option> <option value="email">邮箱</option> <option value="select">选择</option> </select> <div v-if="field.type === 'select'" class="select-options"> <div v-for="(option, optIndex) in field.options" :key="optIndex"> <input v-model="option.value" placeholder="选项值" /> <button @click="removeOption(index, optIndex)">删除</button> </div> <button @click="addOption(index)">添加选项</button> </div> <button @click="removeField(index)">删除字段</button> </div> <button @click="addField">添加字段</button> <button @click="submitForm">提交</button> </div> </template>
<script setup> import { ref, reactive } from 'vue';
const dynamicFields = ref([ { name: '姓名', type: 'text', options: [] } ]);
const addField = () => { dynamicFields.value.push({ name: '', type: 'text', options: [] }); };
const removeField = (index) => { dynamicFields.value.splice(index, 1); };
const addOption = (fieldIndex) => { dynamicFields.value[fieldIndex].options.push({ value: '' }); };
const removeOption = (fieldIndex, optionIndex) => { dynamicFields.value[fieldIndex].options.splice(optionIndex, 1); };
const submitForm = () => { const formData = {}; dynamicFields.value.forEach(field => { if (field.name.trim()) { if (field.type === 'select') { formData[field.name] = field.options.filter(opt => opt.value.trim()).map(opt => opt.value); } else { formData[field.name] = ''; // 实际应用中应该从表单中获取值 } } }); console.log('表单数据:', formData); }; </script>
|
8. 自定义指令
8.1 实现复杂指令
<!-- 自定义指令:点击外部关闭 --> <template> <div> <div v-click-outside="closeMenu" class="dropdown"> 点击我 </div> </div> </template>
<script setup> import { ref } from 'vue';
const closeMenu = () => { console.log('点击外部,关闭菜单'); };
// 自定义指令 const vClickOutside = { mounted(el, binding) { el.clickOutsideEvent = (event) => { if (!(el === event.target || el.contains(event.target))) { binding.value(); } }; document.addEventListener('click', el.clickOutsideEvent); }, unmounted(el) { document.removeEventListener('click', el.clickOutsideEvent); } }; </script>
<!-- 在main.js中全局注册 --> const app = createApp(App); app.directive('click-outside', vClickOutside);
|
8.2 高级指令示例
<!-- 指令:自动聚焦 --> <template> <input v-auto-focus /> </template>
<script setup> const vAutoFocus = { mounted(el) { el.focus(); } }; </script>
<!-- 指令:防抖输入 --> <template> <input v-debounce="500" @input="handleInput" /> </template>
<script setup> import { debounce } from 'lodash-es';
const vDebounce = { mounted(el, binding) { const debouncedFn = debounce(binding.value, binding.arg || 300); el.addEventListener('input', debouncedFn); // 清理函数 el._cleanup = () => { el.removeEventListener('input', debouncedFn); }; }, unmounted(el) { if (el._cleanup) { el._cleanup(); } } };
const handleInput = (e) => { console.log('输入值:', e.target.value); }; </script>
|
9. TypeScript集成
9.1 类型安全的组件
<script setup lang="ts"> import { ref, computed } from 'vue';
interface User { id: number; name: string; email: string; age?: number; }
const users = ref<User[]>([ { id: 1, name: 'John', email: 'john@example.com', age: 25 }, { id: 2, name: 'Jane', email: 'jane@example.com', age: 30 } ]);
const filteredUsers = computed(() => { return users.value.filter(user => user.age && user.age > 25); });
const addUser = (user: Omit<User, 'id'>) => { const newUser = { ...user, id: Date.now() }; users.value.push(newUser); }; </script>
|
9.2 自定义Hook类型
import { ref, computed, Ref } from 'vue';
interface TableColumn { key: string; label: string; sortable?: boolean; }
interface TableSortConfig { key: string | null; direction: 'asc' | 'desc'; }
export function useTable<T>(items: Ref<T[]>, columns: TableColumn[]) { const sortConfig = ref<TableSortConfig>({ key: null, direction: 'asc' });
const sortedItems = computed(() => { const sortableItems = [...items.value]; if (sortConfig.value.key) { sortableItems.sort((a, b) => { const aValue = (a as any)[sortConfig.value.key!]; const bValue = (b as any)[sortConfig.value.key!]; if (aValue < bValue) { return sortConfig.value.direction === 'asc' ? -1 : 1; } if (aValue > bValue) { return sortConfig.value.direction === 'asc' ? 1 : -1; } return 0; }); } return sortableItems; });
const sort = (key: string) => { if (sortConfig.value.key === key) { sortConfig.value.direction = sortConfig.value.direction === 'asc' ? 'desc' : 'asc'; } else { sortConfig.value.key = key; sortConfig.value.direction = 'asc'; } };
return { sortConfig, sortedItems, sort }; }
|
10. 测试策略
10.1 单元测试
import { mount } from '@vue/test-utils'; import Counter from '@/components/Counter.vue';
describe('Counter', () => { it('renders count correctly', () => { const wrapper = mount(Counter); expect(wrapper.text()).toContain('Count: 0'); });
it('increments count when button is clicked', async () => { const wrapper = mount(Counter); await wrapper.find('button').trigger('click'); expect(wrapper.text()).toContain('Count: 1'); });
it('resets count when reset button is clicked', async () => { const wrapper = mount(Counter); await wrapper.find('button:nth-child(2)').trigger('click'); expect(wrapper.text()).toContain('Count: 0'); }); });
|
10.2 组合函数测试
import { useCounter } from '@/hooks/useCounter'; import { nextTick } from 'vue';
describe('useCounter', () => { it('initializes with default value', () => { const { count } = useCounter(); expect(count.value).toBe(0); });
it('increments count', () => { const { count, increment } = useCounter(10); increment(); expect(count.value).toBe(11); });
it('decrements count', () => { const { count, decrement } = useCounter(10); decrement(); expect(count.value).toBe(9); });
it('resets count to initial value', () => { const { count, reset } = useCounter(10); count.value = 20; reset(); expect(count.value).toBe(10); }); });
|
11. 性能监控
<!-- hooks/usePerformance.js --> import { ref, onMounted, onUnmounted } from 'vue';
export function usePerformance() { const metrics = ref({ renderTime: 0, memoryUsage: 0, componentCount: 0 });
const startRender = () => { metrics.value.renderTime = performance.now(); };
const endRender = () => { const renderTime = performance.now() - metrics.value.renderTime; metrics.value.renderTime = renderTime; console.log(`组件渲染时间: ${renderTime}ms`); };
const measureMemory = () => { if (performance.memory) { metrics.value.memoryUsage = performance.memory.usedJSHeapSize; console.log(`内存使用: ${(metrics.value.memoryUsage / 1024 / 1024).toFixed(2)} MB`); } };
return { metrics, startRender, endRender, measureMemory }; }
|
12. 实际项目案例
12.1 电商购物车系统
<!-- stores/cartStore.js --> import { defineStore } from 'pinia';
export const useCartStore = defineStore('cart', { state: () => ({ items: [], isOpen: false }), getters: { totalItems: (state) => state.items.length, totalPrice: (state) => { return state.items.reduce((total, item) => { return total + (item.price * item.quantity); }, 0); }, cartItems: (state) => { return state.items.map(item => ({ ...item, subtotal: item.price * item.quantity })); } }, actions: { addItem(product) { const existingItem = this.items.find(item => item.id === product.id); if (existingItem) { existingItem.quantity += 1; } else { this.items.push({ ...product, quantity: 1 }); } this.openCart(); }, removeItem(productId) { const index = this.items.findIndex(item => item.id === productId); if (index > -1) { this.items.splice(index, 1); } }, updateQuantity(productId, quantity) { const item = this.items.find(item => item.id === productId); if (item) { item.quantity = Math.max(1, quantity); } }, clearCart() { this.items = []; }, toggleCart() { this.isOpen = !this.isOpen; }, openCart() { this.isOpen = true; }, closeCart() { this.isOpen = false; } } });
|
12.2 实时数据流处理
<!-- components/RealTimeData.vue --> <template> <div class="real-time-data"> <div class="data-stats"> <div class="stat"> <h3>总用户数</h3> <p>{{ stats.totalUsers }}</p> </div> <div class="stat"> <h3>在线用户</h3> <p>{{ stats.onlineUsers }}</p> </div> <div class="stat"> <h3>今日活跃</h3> <p>{{ stats.activeToday }}</p> </div> </div> <div class="real-time-chart"> <canvas ref="chartCanvas"></canvas> </div> <div class="recent-activity"> <h3>最近活动</h3> <div class="activity-list"> <div v-for="activity in recentActivities" :key="activity.id" class="activity-item" > <span class="activity-time">{{ formatTime(activity.timestamp) }}</span> <span class="activity-message">{{ activity.message }}</span> </div> </div> </div> </div> </template>
<script setup> import { ref, onMounted, onUnmounted } from 'vue'; import { useWebSocket } from '@/composables/useWebSocket';
const stats = ref({ totalUsers: 0, onlineUsers: 0, activeToday: 0 });
const recentActivities = ref([]);
const { connect, disconnect } = useWebSocket('wss://api.example.com/realtime');
onMounted(() => { connect(); });
onUnmounted(() => { disconnect(); });
const handleData = (data) => { const parsed = JSON.parse(data); if (parsed.type === 'stats') { stats.value = parsed.data; } else if (parsed.type === 'activity') { recentActivities.value.unshift(parsed.data); if (recentActivities.value.length > 10) { recentActivities.value.pop(); } } };
const formatTime = (timestamp) => { return new Date(timestamp).toLocaleTimeString(); }; </script>
|
13. 最佳实践总结
13.1 代码组织原则
- 模块化:将相关功能组织成可复用的模块
- 单一职责:每个函数和组件应该有一个明确的职责
- 可测试性:编写易于测试的代码
- 性能优先:考虑性能影响,优化关键路径
13.2 状态管理最佳实践
- 本地状态:使用ref和reactive管理组件本地状态
- 共享状态:使用Pinia或Vuex管理共享状态
- 状态持久化:考虑使用localStorage或IndexedDB持久化重要状态
- 状态清理:在组件销毁时清理相关状态
13.3 组件设计原则
- 可复用性:设计可复用的通用组件
- 可组合性:使用组合函数创建可复用的逻辑
- 可维护性:保持组件简洁,避免过度复杂
- 可扩展性:设计易于扩展的组件架构
14. 未来发展趋势
14.1 Vue 3的新特性
- Vue DevTools 6.0:更好的开发体验
- 性能优化:更快的响应式系统
- 更好的TypeScript支持:类型推断的改进
- 新的内置组件:更多实用的内置组件
14.2 社区生态发展
- 更多优秀的组件库:基于Composition API的UI库
- 工具链改进:更好的构建工具和开发环境
- 学习资源丰富:更多教程和最佳实践
- 企业级应用:更多大型项目采用Vue 3
15. 结语
Vue 3的Composition API为Vue开发带来了新的可能性,它让代码更加模块化、可复用和类型安全。通过本文介绍的技巧,你应该能够更好地使用Composition API来构建高质量的Vue应用。
记住,优秀的代码不仅功能正确,还要易于维护和扩展。不断实践和学习,你会逐渐掌握Composition API的精髓,成为一名优秀的Vue开发者。
希望本文能够帮助你在Vue 3开发中更加得心应手。如果你有任何问题或建议,欢迎在评论区交流分享!
本文由笔者根据实际项目经验总结,如有疏漏之处,敬请指正。
Vue 3 Composition API高级技巧