如果你是从Vue 2的Options API时代一路走过来的开发者,第一次接触Composition API时,可能会觉得有点“散”。那些曾经规规矩矩待在data、methods、computed选项里的代码,现在被一股脑地放进了setup函数里。但当你真正用它构建过几个复杂组件后,那种逻辑像乐高积木一样自由拼接的快感,会让你再也回不去了。
Options API的核心问题,在业务逻辑简单时并不明显。一旦组件开始变得复杂,比如一个管理后台的筛选表格组件,它可能同时包含数据获取、分页、排序、列配置、表单筛选等多个功能。在Options API下,与“数据获取”相关的代码会被打散:发起请求的created钩子在生命周期里,请求参数和结果数据在data里,请求方法在methods里,处理响应数据的逻辑可能在watch里。你要理解一个完整的功能,眼睛和思维需要在多个选项块之间反复横跳。
Composition API改变了这个范式。它允许你将与同一功能相关的所有代码(状态、计算属性、方法、副作用)聚合在一个地方——通常是一个独立的、可复用的函数(我们称之为“组合式函数”)。
// useTableData.js - 一个处理表格数据的组合式函数
import { ref, computed, onMounted } from 'vue';
import { fetchTableList } from '@/api';
export function useTableData(initialQuery = {}) {
const data = ref([]);
const loading = ref(false);
const query = ref({ ...initialQuery });
const pagination = ref({ current: 1, pageSize: 10, total: 0 });
const isEmpty = computed(() => !loading.value && data.value.length === 0);
const loadData = async () => {
loading.value = true;
try {
const res = await fetchTableList({
...query.value,
page: pagination.value.current,
size: pagination.value.pageSize
});
data.value = res.list;
pagination.value.total = res.total;
} finally {
loading.value = false;
}
};
const handlePageChange = (pageInfo) => {
pagination.value.current = pageInfo.current;
loadData();
};
onMounted(loadData);
return {
data,
loading,
query,
pagination,
isEmpty,
loadData,
handlePageChange
};
}
然后在组件里,你可以像搭积木一样使用它:
// MyTable.vue
import { useTableData } from './useTableData';
import { useColumnConfig } from './useColumnConfig';
export default {
setup() {
// 表格数据逻辑
const { data, loading, pagination, handlePageChange } = useTableData();
// 表格列配置逻辑
const { columns, visibleColumns } = useColumnConfig();
return {
data,
loading,
columns,
visibleColumns,
pagination,
handlePageChange
};
}
};
这种写法带来的最直接好处是可读性的质变。阅读组件时,你不再需要像侦探一样拼凑线索,每个功能模块都是一个自包含的、高内聚的单元。调试和重构也变得异常清晰,想修改数据获取逻辑?直接去useTableData函数里找,所有相关代码都在那儿。
Vue 2对TypeScript的支持一直有些“拧巴”,尤其是this上下文的类型推导,总感觉隔着一层纱。Composition API几乎是为TypeScript量身定做的。因为它的核心是普通的JavaScript函数,返回普通的JavaScript变量,TypeScript可以毫无障碍地进行完美的类型推断。
上面那个useTableData函数,你可以轻松地为它加上精确的类型定义:
import type { Ref } from 'vue';
interface TableItem {
id: number;
name: string;
status: 'active' | 'inactive';
}
interface Pagination {
current: number;
pageSize: number;
total: number;
}
interface QueryParams {
keyword?: string;
status?: TableItem['status'];
}
export function useTableData(initialQuery: QueryParams = {}) {
const data: Ref<TableItem[]> = ref([]);
const query: Ref<QueryParams> = ref({ ...initialQuery });
const pagination: Ref<Pagination> = ref({ current: 1, pageSize: 10, total: 0 });
// ... 其他逻辑
return {
data, // 类型: Ref<TableItem[]>
query, // 类型: Ref<QueryParams>
pagination, // 类型: Ref<Pagination>
// ... 其他返回属性也都有精确类型
};
}
在消费这个函数的组件中,你的IDE会提供无比精确的自动补全和类型检查。鼠标悬停在data.value上,立刻知道它是一个TableItem数组;调用handlePageChange时,参数类型不对立刻报错。这种开发体验,极大地减少了因类型错误导致的运行时bug,也让阅读他人代码或接手老项目时,理解数据结构的成本大幅降低。
很多人把Composition API的优势简单归结为“更好的逻辑复用”,这其实看浅了。Options API的Mixins也能实现某种程度的复用,但Mixins带来的命名冲突和来源不清问题同样棘手。
Composition API更深远的价值在于,它提供了一种标准化、声明式的逻辑抽象能力。一个设计良好的组合式函数,就像一个设计良好的React Hook,它不仅仅是一段可复用的代码,更是一个清晰的、有明确输入输出的“逻辑契约”。
团队可以将项目中常见的业务模式沉淀为一个个组合式函数,比如useFormValidation、useModalControl、usePermission。新成员加入项目,不需要从头理解一堆散落在各处的业务规则,他只需要知道“哦,表单验证用这个useFormValidation函数,它返回这些方法和状态”。这实际上是在代码层面构建了一套团队的“业务逻辑SDK”,极大地提升了大型项目的可维护性和团队协作效率。
说到底,Composition API提升的不仅是“开发效率”,更是一种“开发心智”。它让前端开发者从“如何把代码放进Vue规定的选项里”的思维中解放出来,回归到“如何更好地组织我的程序逻辑”这个更本质的问题上。当你习惯了这种思维方式,你会发现,编写Vue组件和编写任何其他良好的JavaScript模块,并没有什么不同。
参与讨论
用起来确实比Options API顺手多了,逻辑聚合很清晰
这个useTableData的例子很实用,准备在项目里试试看
TypeScript支持确实好了很多,类型推导比以前强太多了
从Vue2转过来有点不习惯,但用熟后发现真香
组合式函数写起来像搭积木,维护起来方便多了