组合式 API 解决了什么问题
选项式 API 把代码按「数据类型」切块:数据放一处、方法放一处、监听与生命周期各放一处。当组件只做一件事时它非常清晰,但一个管理页往往同时处理筛选、分页、权限与表单,相关逻辑被拆散到四五个选项中,读代码要在文件里来回跳。组合式 API 按「业务关注点」组织,同一件事的状态、计算与副作用写在一起,还可以整体抽成可复用的函数。
组合式 API 的价值不在写法新潮,而在于它让「一段可复用的逻辑」终于有了天然的载体——一个普通函数。
- setup组件的入口,在其中声明状态、计算与副作用。
- ref包装基础类型,模板中自动解包,脚本中通过
.value读写。 - reactive代理对象,属性直接读写,注意解构会丢失响应性。
- 组合函数以
use开头的普通函数,是逻辑复用的基本单位。
setup 与响应式核心:ref 与 reactive
ref 能包装任意类型,是最不容易出错的选择;reactive 只接受对象,适合表示一组内聚的状态。两者的替换代价差别很大:ref 在模板里自动解包,在脚本里要写 .value;reactive 直接访问属性,但一旦被解构或整体重新赋值,响应性就断了。团队内部最好统一约定,避免两种风格混用带来的心智负担。
import { ref, reactive, computed } from "vue";
const keyword = ref("");
const list = ref([]);
const pager = reactive({ page: 1, size: 20 });
const filtered = computed(() =>
list.value.filter((it) => it.name.includes(keyword.value)));
async function load() {
list.value = await fetch("/api/list").then((r) => r.json());
}
这段代码里 computed 是关键:它根据关键词派生结果,关键词一变自动重算。把派生值写成计算属性而不是存储状态,就不会出现「列表更新了但筛选结果没变」这类不一致。
计算属性与侦听器
计算属性与侦听器的分工很明确:前者根据已有状态推导新值,应当是纯计算、无副作用;后者响应变化去执行动作,例如发请求、写缓存、上报埋点。选错会导致两种典型问题——用监听器维护派生状态,会出现短暂的不一致窗口;用计算属性发请求,则会在渲染过程中触发副作用,造成难以追踪的重复请求。
| 场景 | 该用哪个 | 原因 |
|---|---|---|
| 根据筛选条件出结果 | 计算属性 | 纯派生,可缓存 |
| 关键词变化后请求接口 | 侦听器 | 需要副作用与防抖 |
| 格式化展示文本 | 计算属性 | 同一输入结果稳定 |
侦听器还提供「立即执行」与深度监听两个开关。深度监听会遍历整个对象,对大型数据结构有明显开销,应尽量改为监听具体字段,或者在数据源头保证引用的更新粒度足够小。
组件通信与插槽
Vue 的通信路径很清楚:父传子用属性,子传父用事件,跨层级用依赖注入,全局共享用状态库。插槽则解决了「结构由父级决定、内容由子级渲染」的场景——一个卡片组件只需要暴露标题和工具栏两个插槽,就能适配完全不同的业务内容,而不必为每种样式新增一个属性开关。
- 属性与事件默认选择,显式、易调试,适合一到两层的传递。
- 依赖注入主题、国际化这类深层共享值,避免层层透传。
- 状态库跨页面共享的业务状态,配合模块化拆分。
- 插槽不涉及数据的结构定制,用默认插槽与具名插槽即可。
响应式原理与性能取舍
Vue 3 用代理对象拦截读写:读取时收集依赖,写入时通知相关的副作用重新执行。这套机制决定了性能优化的方向——减少被追踪的属性数量、避免在同一个副作用里依赖过多无关状态。实践中,把大对象拆成若干小 ref,往往比给一个大对象做深度监听更高效。
模板渲染同样是可优化的路径:长列表配上稳定的 key 与按需渲染、频繁切换的区块用 v-show 而非 v-if、列表项内避免使用高开销的高阶组件包装层。这些改动的收益通常比调整响应式写法更直接,也更值得先做。
工程实践与常见坑
第一个坑是解构响应式对象。const { page } = reactive({ page: 1 }) 会立刻把值掏出来,后续修改不再被追踪;正确做法是用 toRefs 保持引用,或者干脆改用 ref。第二个坑是把组合函数里的状态定义在外层作用域,多个组件实例会共享同一份状态,表现为「一处输入,多处一起变」。
第三个坑是异步组件的加载状态处理不当:数据还没回来就渲染,模板里访问了空对象的属性而报错。稳妥的写法是让初始值具备完整的结构,或者统一在模板外层用条件渲染兜底,而不是在每处访问前判空。
// 组合函数:每次调用都会创建独立的状态
export function usePager(initialSize = 20) {
const page = ref(1);
const size = ref(initialSize);
const reset = () => { page.value = 1; };
return { page, size, reset };
}
构建层面还要注意:路由级组件做代码分割、第三方库按需引入、生产构建开启压缩与资源指纹。这三项做齐,首屏体积通常能降下一大截,效果远好于在业务代码里抠细节。
小结与延伸阅读
组合式 API 的心法是「按业务关注点组织代码,把可复用的部分抽成函数」,剩下的都是这条心法的推论。
- 需要对比另一种组件模型,读《React 18 实战:组件化开发》。
- 类型约束上补课,读《TypeScript 类型系统精讲》。
- 异步与模块基础,见《JavaScript 核心进阶:异步与模块化》。