引言
Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。相比于传统的 Options API,Composition API 提供了更灵活、更强大的组件状态管理方式。本文将深入探讨 Vue 3 Composition API 的核心概念、使用技巧以及最佳实践,帮助开发者构建现代化的前端应用。
什么是 Composition API
基础概念
Composition API 是 Vue 3 中引入的一种新的组件状态管理方式,它允许我们以函数的形式组织和复用逻辑代码。与传统的 Options API(如 data、methods、computed、watch 等选项)不同,Composition API 更加灵活,能够更好地处理复杂的组件逻辑。
与 Options API 的对比
在 Vue 2 中,我们通常使用 Options API 来组织组件逻辑:
export default {
data() {
return {
count: 0,
name: 'Vue'
}
},
computed: {
doubledCount() {
return this.count * 2;
}
},
methods: {
increment() {
this.count++;
}
},
mounted() {
console.log('组件已挂载');
}
}
而在 Vue 3 中,我们可以使用 Composition API 来实现相同的功能:
import { ref, computed, onMounted } from 'vue';
export default {
setup() {
const count = ref(0);
const name = ref('Vue');
const doubledCount = computed(() => count.value * 2);
const increment = () => {
count.value++;
};
onMounted(() => {
console.log('组件已挂载');
});
return {
count,
name,
doubledCount,
increment
};
}
}
响应式数据处理
ref 和 reactive 的基本使用
ref 的使用
ref 是 Vue 3 中最基本的响应式数据包装器,它可以为任意类型的数据创建响应式引用:
import { ref } from 'vue';
// 创建基础类型的响应式数据
const count = ref(0);
const message = ref('Hello Vue');
// 访问和修改值
console.log(count.value); // 0
count.value = 10;
console.log(count.value); // 10
// 在模板中使用
// <template>
// <p>{{ count }}</p>
// </template>
reactive 的使用
reactive 用于创建响应式对象,它会将对象的所有属性都转换为响应式:
import { reactive } from 'vue';
const state = reactive({
count: 0,
name: 'Vue',
user: {
age: 25,
email: 'vue@example.com'
}
});
// 修改属性
state.count = 10;
state.user.age = 30;
// 在模板中使用
// <template>
// <p>{{ state.count }}</p>
// <p>{{ state.user.age }}</p>
// </template>
深层响应式对象处理
对于嵌套的对象,Vue 3 会自动将其转换为响应式:
import { reactive } from 'vue';
const user = reactive({
profile: {
personal: {
name: 'John',
age: 25
}
}
});
// 这样修改是响应式的
user.profile.personal.name = 'Jane';
// 或者
user.profile = {
...user.profile,
personal: {
...user.profile.personal,
name: 'Jane'
}
};
readonly 和 shallowReactive
readonly
readonly 可以创建一个只读的响应式对象:
import { reactive, readonly } from 'vue';
const original = reactive({ count: 0 });
const copy = readonly(original);
// 修改会触发警告,但不会生效
copy.count = 1; // 警告:不能修改只读属性
shallowReactive
shallowReactive 只将对象顶层属性转换为响应式:
import { shallowReactive } from 'vue';
const state = shallowReactive({
nested: { count: 0 },
array: [1, 2, 3]
});
// 这样修改是响应式的
state.nested.count = 1; // 不会触发响应式更新
state.array.push(4); // 会触发响应式更新
组合函数复用
创建组合函数
组合函数是 Composition API 的核心概念之一,它允许我们将可复用的逻辑封装成函数:
// composables/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 doubled = computed(() => count.value * 2);
return {
count,
increment,
decrement,
reset,
doubled
};
}
使用组合函数
// 在组件中使用
import { useCounter } from '@/composables/useCounter';
export default {
setup() {
const { count, increment, decrement, reset, doubled } = useCounter(10);
return {
count,
increment,
decrement,
reset,
doubled
};
}
};
更复杂的组合函数示例
// composables/useFetch.js
import { ref, watch } from 'vue';
export function useFetch(url) {
const data = ref(null);
const loading = ref(false);
const error = ref(null);
const fetchData = async () => {
loading.value = true;
error.value = null;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
data.value = await response.json();
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
};
// 立即执行
fetchData();
// 监听 url 变化,重新获取数据
watch(url, fetchData);
return {
data,
loading,
error,
refetch: fetchData
};
}
生命周期钩子
基本生命周期钩子
Composition API 提供了与 Vue 2 相同的生命周期钩子,但以函数的形式提供:
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured,
onRenderTracked,
onRenderTriggered
} from 'vue';
export default {
setup() {
onBeforeMount(() => {
console.log('组件即将挂载');
});
onMounted(() => {
console.log('组件已挂载');
});
onBeforeUpdate(() => {
console.log('组件即将更新');
});
onUpdated(() => {
console.log('组件已更新');
});
onBeforeUnmount(() => {
console.log('组件即将卸载');
});
onUnmounted(() => {
console.log('组件已卸载');
});
return {};
}
};
生命周期钩子的最佳实践
// 带有副作用清理的生命周期钩子
import { ref, onMounted, onUnmounted } from 'vue';
export default {
setup() {
const timer = ref(null);
const count = ref(0);
// 设置定时器
onMounted(() => {
timer.value = setInterval(() => {
count.value++;
}, 1000);
});
// 清理定时器
onUnmounted(() => {
if (timer.value) {
clearInterval(timer.value);
}
});
return { count };
}
};
计算属性和侦听器
computed 计算属性
import { ref, computed } from 'vue';
export default {
setup() {
const firstName = ref('John');
const lastName = ref('Doe');
// 基本计算属性
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});
// 带 getter 和 setter 的计算属性
const fullNameWithSetter = computed({
get: () => `${firstName.value} ${lastName.value}`,
set: (value) => {
const names = value.split(' ');
firstName.value = names[0];
lastName.value = names[1];
}
});
return {
firstName,
lastName,
fullName,
fullNameWithSetter
};
}
};
watch 侦听器
import { ref, watch, watchEffect } from 'vue';
export default {
setup() {
const count = ref(0);
const name = ref('Vue');
// 基本侦听器
watch(count, (newVal, oldVal) => {
console.log(`count 从 ${oldVal} 变为 ${newVal}`);
});
// 侦听多个源
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
console.log(`count: ${oldCount} -> ${newCount}, name: ${oldName} -> ${newName}`);
});
// 深度侦听
const user = ref({
profile: {
name: 'John'
}
});
watch(user, (newVal, oldVal) => {
console.log('用户信息发生变化');
}, { deep: true });
// 立即执行的侦听器
watch(count, (newVal) => {
console.log(`count 变为 ${newVal}`);
}, { immediate: true });
// watchEffect
const effect = watchEffect(() => {
console.log(`当前 count 值: ${count.value}`);
// 当 count 改变时,这个函数会重新执行
});
// 清理副作用
const cleanup = watchEffect((onInvalidate) => {
const timer = setTimeout(() => {
console.log('定时器执行');
}, 1000);
onInvalidate(() => {
clearTimeout(timer);
console.log('清理定时器');
});
});
return { count, name };
}
};
组件间通信
Props 和 emits
// 子组件
import { ref } from 'vue';
export default {
props: {
title: {
type: String,
required: true
},
count: {
type: Number,
default: 0
}
},
emits: ['update-count', 'submit'],
setup(props, { emit }) {
const localCount = ref(props.count);
const handleIncrement = () => {
localCount.value++;
emit('update-count', localCount.value);
};
const handleSubmit = () => {
emit('submit', { count: localCount.value });
};
return {
localCount,
handleIncrement,
handleSubmit
};
}
};
// 父组件
import { ref } from 'vue';
export default {
setup() {
const count = ref(0);
const title = ref('我的组件');
const handleUpdateCount = (newCount) => {
count.value = newCount;
};
const handleSubmit = (data) => {
console.log('提交数据:', data);
};
return {
count,
title,
handleUpdateCount,
handleSubmit
};
}
};
provide 和 inject
// 父组件
import { provide, ref } from 'vue';
export default {
setup() {
const theme = ref('dark');
const user = ref({ name: 'John', role: 'admin' });
provide('theme', theme);
provide('user', user);
provide('updateTheme', (newTheme) => {
theme.value = newTheme;
});
return { theme, user };
}
};
// 子组件
import { inject } from 'vue';
export default {
setup() {
const theme = inject('theme');
const user = inject('user');
const updateTheme = inject('updateTheme');
const toggleTheme = () => {
updateTheme(theme.value === 'dark' ? 'light' : 'dark');
};
return {
theme,
user,
toggleTheme
};
}
};
状态管理模式
基础状态管理
// stores/useGlobalStore.js
import { reactive } from 'vue';
export function useGlobalStore() {
const state = reactive({
user: null,
isLoggedIn: false,
theme: 'light'
});
const setUser = (user) => {
state.user = user;
state.isLoggedIn = !!user;
};
const setTheme = (theme) => {
state.theme = theme;
};
const logout = () => {
state.user = null;
state.isLoggedIn = false;
};
return {
state,
setUser,
setTheme,
logout
};
}
使用状态管理
// App.vue
import { useGlobalStore } from '@/stores/useGlobalStore';
import { onMounted } from 'vue';
export default {
setup() {
const { state, setUser, setTheme, logout } = useGlobalStore();
onMounted(() => {
// 模拟从 API 获取用户信息
setTimeout(() => {
setUser({
id: 1,
name: 'John Doe',
email: 'john@example.com'
});
}, 1000);
});
return {
state,
logout,
setTheme
};
}
};
复杂状态管理示例
// stores/useTodoStore.js
import { ref, computed } from 'vue';
export function useTodoStore() {
const todos = ref([]);
const filter = ref('all');
// 添加待办事项
const addTodo = (text) => {
const newTodo = {
id: Date.now(),
text,
completed: false,
createdAt: new Date()
};
todos.value.push(newTodo);
};
// 切换完成状态
const toggleTodo = (id) => {
const todo = todos.value.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
};
// 删除待办事项
const deleteTodo = (id) => {
todos.value = todos.value.filter(t => t.id !== id);
};
// 清除已完成的待办事项
const clearCompleted = () => {
todos.value = todos.value.filter(t => !t.completed);
};
// 计算过滤后的待办事项
const filteredTodos = computed(() => {
switch (filter.value) {
case 'active':
return todos.value.filter(todo => !todo.completed);
case 'completed':
return todos.value.filter(todo => todo.completed);
default:
return todos.value;
}
});
// 计算未完成的待办事项数量
const activeCount = computed(() => {
return todos.value.filter(todo => !todo.completed).length;
});
// 计算已完成的待办事项数量
const completedCount = computed(() => {
return todos.value.filter(todo => todo.completed).length;
});
// 设置过滤器
const setFilter = (newFilter) => {
filter.value = newFilter;
};
return {
todos,
filter,
filteredTodos,
activeCount,
completedCount,
addTodo,
toggleTodo,
deleteTodo,
clearCompleted,
setFilter
};
}
高级技巧和最佳实践
响应式数据的性能优化
// 使用 computed 缓存复杂计算
import { ref, computed } from 'vue';
export default {
setup() {
const items = ref([]);
const searchTerm = ref('');
// 复杂的过滤计算,使用 computed 缓存
const filteredItems = computed(() => {
if (!searchTerm.value) return items.value;
return items.value.filter(item =>
item.name.toLowerCase().includes(searchTerm.value.toLowerCase())
);
});
// 对于非常复杂的计算,可以使用缓存策略
const expensiveCalculation = computed({
get: () => {
// 复杂的计算逻辑
return items.value.reduce((sum, item) => sum + item.value, 0);
},
set: (value) => {
// 可以设置 setter 来更新基础数据
console.log('计算结果:', value);
}
});
return {
items,
searchTerm,
filteredItems,
expensiveCalculation
};
}
};
组合函数的测试
// composables/__tests__/useCounter.spec.js
import { useCounter } from '../useCounter';
import { nextTick } from 'vue';
describe('useCounter', () => {
it('should initialize with correct value', () => {
const { count } = useCounter(5);
expect(count.value).toBe(5);
});
it('should increment correctly', () => {
const { count, increment } = useCounter();
increment();
expect(count.value).toBe(1);
});
it('should decrement correctly', () => {
const { count, decrement } = useCounter(10);
decrement();
expect(count.value).toBe(9);
});
it('should reset correctly', () => {
const { count, reset } = useCounter(5);
count.value = 10;
reset();
expect(count.value).toBe(5);
});
});
错误处理和边界情况
// composables/useAsyncData.js
import { ref, watch } from 'vue';
export function useAsyncData(fetcher, options = {}) {
const data = ref(null);
const loading = ref(false);
const error = ref(null);
const retryCount = ref(0);
const fetchData = async (params = {}) => {
if (loading.value) return;
loading.value = true;
error.value = null;
try {
const result = await fetcher(params);
data.value = result;
retryCount.value = 0;
} catch (err) {
error.value = err;
console.error('数据获取失败:', err);
// 重试逻辑
if (options.retry && retryCount.value < options.retry.maxAttempts) {
retryCount.value++;
setTimeout(() => fetchData(params), options.retry.delay || 1000);
}
} finally {
loading.value = false;
}
};
// 自动重新获取数据
if (options.autoFetch !== false) {
fetchData();
}
const refresh = () => fetchData();
return {
data,
loading,
error,
retryCount,
refresh,
fetchData
};
}
TypeScript 支持
// composables/useTypedCounter.ts
import { ref, computed } from 'vue';
interface CounterState {
count: number;
name: string;
}
export function useTypedCounter(initialValue: number = 0) {
const count = ref<number>(initialValue);
const name = ref<string>('Vue');
const doubled = computed<number>(() => count.value * 2);
const increment = () => {
count.value++;
};
const decrement = () => {
count.value--;
};
const reset = (value: number = initialValue) => {
count.value = value;
};
return {
count,
name,
doubled,
increment,
decrement,
reset
};
}
总结
Vue 3 的 Composition API 为前端开发带来了前所未有的灵活性和强大功能。通过本文的详细介绍,我们了解了:
- 响应式数据处理:掌握了 ref 和 reactive 的基本使用以及高级特性
- 组合函数复用:学会了如何创建可复用的逻辑模块
- 生命周期管理:理解了如何在 Composition API 中使用生命周期钩子
- 计算属性和侦听器:掌握了复杂的响应式编程技巧
- 组件间通信:了解了 props、emits、provide 和 inject 的使用
- 状态管理模式:学习了如何构建复杂的状态管理解决方案
Composition API 不仅让代码更加模块化和可复用,还提供了更好的类型支持和开发体验。随着 Vue 3 的普及,掌握 Composition API 已经成为现代前端开发者必备的技能。
通过实践这些最佳实践,我们可以构建出更加健壮、可维护的 Vue 应用程序。记住,在使用 Composition API 时要保持代码的简洁性,合理组织逻辑,充分利用组合函数来提高代码复用率。

评论 (0)