𝑻𝒆𝒏𝑪𝒍𝒂𝒘正在头脑风暴···
𝑻𝒆𝒏𝑲𝒊𝑺𝒆𝒀𝒂の𝑨𝒈𝒆𝒏𝒕助手
𝑻𝒆𝒏-𝒇𝒍𝒂𝒔𝒉

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类型

// hooks/useTable.ts
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 单元测试

// components/Counter.spec.ts
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 组合函数测试

// hooks/useCounter.spec.ts
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 代码组织原则

  1. 模块化:将相关功能组织成可复用的模块
  2. 单一职责:每个函数和组件应该有一个明确的职责
  3. 可测试性:编写易于测试的代码
  4. 性能优先:考虑性能影响,优化关键路径

13.2 状态管理最佳实践

  1. 本地状态:使用ref和reactive管理组件本地状态
  2. 共享状态:使用Pinia或Vuex管理共享状态
  3. 状态持久化:考虑使用localStorage或IndexedDB持久化重要状态
  4. 状态清理:在组件销毁时清理相关状态

13.3 组件设计原则

  1. 可复用性:设计可复用的通用组件
  2. 可组合性:使用组合函数创建可复用的逻辑
  3. 可维护性:保持组件简洁,避免过度复杂
  4. 可扩展性:设计易于扩展的组件架构

14. 未来发展趋势

14.1 Vue 3的新特性

  1. Vue DevTools 6.0:更好的开发体验
  2. 性能优化:更快的响应式系统
  3. 更好的TypeScript支持:类型推断的改进
  4. 新的内置组件:更多实用的内置组件

14.2 社区生态发展

  1. 更多优秀的组件库:基于Composition API的UI库
  2. 工具链改进:更好的构建工具和开发环境
  3. 学习资源丰富:更多教程和最佳实践
  4. 企业级应用:更多大型项目采用Vue 3

15. 结语

Vue 3的Composition API为Vue开发带来了新的可能性,它让代码更加模块化、可复用和类型安全。通过本文介绍的技巧,你应该能够更好地使用Composition API来构建高质量的Vue应用。

记住,优秀的代码不仅功能正确,还要易于维护和扩展。不断实践和学习,你会逐渐掌握Composition API的精髓,成为一名优秀的Vue开发者。

希望本文能够帮助你在Vue 3开发中更加得心应手。如果你有任何问题或建议,欢迎在评论区交流分享!


本文由笔者根据实际项目经验总结,如有疏漏之处,敬请指正。