Vue 3 Composition API深度解析:从Options到Composition
Vue 3的Composition API是Vue生态系统中最重大的变革之一。它彻底改变了我们组织和重用Vue组件逻辑的方式。本文将深入探讨Composition API的核心概念、优势以及实际应用。
1. Options API的局限性
在Vue 2中,我们使用Options API组织组件代码。虽然直观,但随着组件复杂度增加,会出现一些问题:
// Options API示例 - 逻辑分散
export default {
data() {
return {
count: 0,
searchQuery: "",
filteredItems: []
};
},
computed: {
doubleCount() {
return this.count * 2;
},
searchResults() {
return this.items.filter(item =>
item.name.includes(this.searchQuery)
);
}
},
watch: {
searchQuery(newVal) {
this.fetchResults(newVal);
}
},
methods: {
increment() {
this.count++;
},
fetchResults(query) {
// 获取搜索结果
}
},
mounted() {
this.fetchResults(this.searchQuery);
}
};
问题:相关逻辑分散在不同的选项中,难以维护和重用。
2. Composition API基础
Composition API通过setup()函数将相关逻辑组织在一起:
// Composition API基础示例
import { ref, computed, watch, onMounted } from "vue";
export default {
setup() {
// 计数器逻辑
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
const increment = () => {
count.value++;
};
// 搜索逻辑
const searchQuery = ref("");
const items = ref([]);
const searchResults = computed(() => {
return items.value.filter(item =>
item.name.includes(searchQuery.value)
);
});
const fetchResults = async (query) => {
const response = await fetch(`/api/search?q=${query}`);
items.value = await response.json();
};
watch(searchQuery, (newVal) => {
fetchResults(newVal);
});
onMounted(() => {
fetchResults(searchQuery.value);
});
return {
count,
doubleCount,
increment,
searchQuery,
searchResults
};
}
};
3. 自定义组合函数
Composition API的真正威力在于可重用的组合函数:
// 自定义组合函数:useCounter
import { ref, computed } from "vue";
export function useCounter(initialValue = 0) {
const count = ref(initialValue);
const doubleCount = computed(() => count.value * 2);
const increment = () => count.value++;
const decrement = () => count.value--;
const reset = () => count.value = initialValue;
return {
count,
doubleCount,
increment,
decrement,
reset
};
}
// 自定义组合函数:useSearch
import { ref, computed, watch } from "vue";
export function useSearch(initialQuery = "", fetchFn) {
const searchQuery = ref(initialQuery);
const results = ref([]);
const isLoading = ref(false);
const error = ref(null);
const performSearch = async () => {
if (!searchQuery.value.trim()) {
results.value = [];
return;
}
isLoading.value = true;
error.value = null;
try {
results.value = await fetchFn(searchQuery.value);
} catch (err) {
error.value = err.message;
results.value = [];
} finally {
isLoading.value = false;
}
};
// 防抖搜索
let timeoutId;
watch(searchQuery, () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(performSearch, 300);
}, { immediate: true });
return {
searchQuery,
results,
isLoading,
error,
performSearch
};
}
// 在组件中使用组合函数
import { useCounter, useSearch } from "./composables";
export default {
setup() {
const counter = useCounter(10);
const search = useSearch("", async (query) => {
const response = await fetch(`/api/products?q=${query}`);
return response.json();
});
return {
...counter,
...search
};
}
};
4. 高级模式
4.1 依赖注入
// 提供依赖
import { provide, inject } from "vue";
// 创建上下文
const UserContext = Symbol("user");
export function provideUser(user) {
provide(UserContext, user);
}
export function useUser() {
const user = inject(UserContext);
if (!user) {
throw new Error("useUser必须在provideUser的组件内使用");
}
return user;
}
// 在父组件中
import { provideUser } from "./userContext";
export default {
setup() {
const user = reactive({
id: 1,
name: "张三",
email: "zhangsan@example.com"
});
provideUser(user);
return { user };
}
};
// 在子组件中
import { useUser } from "./userContext";
export default {
setup() {
const user = useUser();
return { user };
}
};
4.2 生命周期钩子组合
// 组合生命周期逻辑
import { onMounted, onUnmounted, ref } from "vue";
export function useEventListener(target, event, callback) {
onMounted(() => {
target.addEventListener(event, callback);
});
onUnmounted(() => {
target.removeEventListener(event, callback);
});
}
export function useInterval(callback, delay) {
let intervalId;
onMounted(() => {
intervalId = setInterval(callback, delay);
});
onUnmounted(() => {
clearInterval(intervalId);
});
}
// 在组件中使用
import { useEventListener, useInterval } from "./composables";
export default {
setup() {
const windowWidth = ref(window.innerWidth);
// 监听窗口大小变化
useEventListener(window, "resize", () => {
windowWidth.value = window.innerWidth;
});
// 每秒更新一次时间
const currentTime = ref(new Date());
useInterval(() => {
currentTime.value = new Date();
}, 1000);
return { windowWidth, currentTime };
}
};
5. 最佳实践
- 单一职责:每个组合函数只做一件事
- 命名规范:使用use前缀命名组合函数
- 参数设计:提供合理的默认值和配置选项
- 返回值:返回响应式对象,保持一致性
- 错误处理:在组合函数中处理错误
- 类型安全:使用TypeScript获得更好的类型支持
// TypeScript示例
import { ref, Ref, computed, ComputedRef } from "vue";
interface UsePaginationOptions {
items: T[];
itemsPerPage?: number;
}
interface UsePaginationReturn {
currentPage: Ref;
totalPages: ComputedRef;
paginatedItems: ComputedRef;
nextPage: () => void;
prevPage: () => void;
goToPage: (page: number) => void;
}
export function usePagination(
options: UsePaginationOptions
): UsePaginationReturn {
const {
items,
itemsPerPage = 10
} = options;
const currentPage = ref(1);
const totalPages = computed(() =>
Math.ceil(items.length / itemsPerPage)
);
const paginatedItems = computed(() => {
const start = (currentPage.value - 1) * itemsPerPage;
const end = start + itemsPerPage;
return items.slice(start, end);
});
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
}
};
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
}
};
const goToPage = (page: number) => {
if (page >= 1 && page <= totalPages.value) {
currentPage.value = page;
}
};
return {
currentPage,
totalPages,
paginatedItems,
nextPage,
prevPage,
goToPage
};
}
总结
Vue 3的Composition API不仅解决了Options API在复杂组件中的维护问题,更重要的是它提供了一种全新的逻辑组织和重用范式。通过自定义组合函数,我们可以:
- 将相关逻辑组织在一起,提高代码可读性
- 创建可重用的逻辑单元,减少代码重复
- 更好地进行类型推导(配合TypeScript)
- 实现更灵活的逻辑组合方式
对于新项目,强烈推荐使用Composition API。对于现有Vue 2项目,可以逐步迁移,先从新组件开始使用Composition API,然后逐步重构复杂组件。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END




暂无评论内容