Vue 3 的 Composition API 是一套全新的代码组织方式,旨在解决 Options API 在处理复杂组件逻辑时可能出现的碎片化和逻辑复用困难的问题。它不是取代 Options API,而是提供了一种更灵活、更强大的替代方案,尤其适合大型项目或逻辑复杂的组件。
核心思想:
- 基于函数组织逻辑: 将组件的逻辑(数据、计算属性、方法、生命周期钩子、watch 等)封装在可复用的函数中(称为 Composable Functions 或简称 Composables)。
- 逻辑关注点聚合: 将相关的代码(例如一个功能模块的所有逻辑)集中在一起,而不是分散在
data,methods,computed,watch,lifecycle hooks等不同的选项中。 - 更好的 TypeScript 集成: 对 TypeScript 的类型推断更友好。
核心 API 详解:
-
setup()函数:- 入口点: Composition API 的核心入口。它在组件实例创建之前执行(在
beforeCreate和created生命周期钩子之前),且this在setup中不可用(因为组件实例尚未完全创建)。 - 参数:
props: 组件接收的 props 对象,是响应式的。不要解构props,否则会失去响应性!如需解构,使用toRefs(props)或toRef(props, 'propName')。context: 一个普通对象,包含三个属性:attrs: 非 prop 的 attribute(同this.$attrs)。slots: 插槽内容(同this.$slots)。emit: 触发事件的方法(同this.$emit)。
- 返回值: 必须返回一个对象或一个渲染函数。返回的对象中的属性和方法会暴露给组件的模板(template)和选项中的
this。
import { toRefs } from 'vue'; export default { props: { title: String }, setup(props, { emit }) { // 正确访问响应式 props const { title } = toRefs(props); // title 现在是 ref console.log(title.value); // 定义响应式数据、方法等... const count = ref(0); function increment() { count.value++; emit('count-changed', count.value); } // 暴露给模板和 this return { title, // 注意:这里暴露的是 ref,模板会自动解包 .value count, increment }; } } - 入口点: Composition API 的核心入口。它在组件实例创建之前执行(在
-
响应式基础 (
ref,reactive):ref(value):- 创建一个响应式引用对象,包含一个
.value属性指向内部值。 - 适用于基本类型 (string, number, boolean, null, undefined, symbol) 或任何类型(对象、数组也可以,但通常对象用
reactive)。 - 在模板中使用会自动解包(无需写
.value),在 JS 中访问或修改必须用.value。 - 创建:
const count = ref(0); - 访问:
console.log(count.value); // 0 - 修改:
count.value = 1;
- 创建一个响应式引用对象,包含一个
reactive(object):- 创建一个响应式代理对象(Proxy)。
- 适用于对象或数组。
- 访问和修改属性直接使用对象属性语法,无需
.value。 - 创建:
const state = reactive({ count: 0, name: 'Vue' }); - 访问/修改:
console.log(state.count); state.count++; state.name = 'Vue 3';
- 关键区别:
ref用于基本类型或需要保持引用的对象,通过.value访问。reactive用于对象/数组,直接访问属性。- 使用
reactive的对象,如果整体替换会失去响应性(state = newObject不行)。ref的.value可以被替换(count.value = newValue是响应式的)。 - 使用
toRefs(reactiveObject)可以将reactive对象的每个属性转换为独立的ref,便于在返回对象或解构时保持响应性。
-
计算属性 (
computed):- 接收一个 getter 函数,返回一个只读的响应式 ref 对象。
- 也可以接收一个包含
get和set函数的对象,创建可写的 ref。 - 只读:
const doubledCount = computed(() => count.value * 2); console.log(doubledCount.value); // 访问值 - 可写:
const writableComputed = computed({ get: () => count.value + 1, set: (val) => { count.value = val - 1; } }); writableComputed.value = 10; // 触发 set, count.value 变为 9
-
侦听器 (
watch,watchEffect):watch(source, callback, options?):- 显式指定要侦听的一个或多个响应式数据源。
source:可以是 getter 函数、ref、reactive对象、或包含前几种类型的数组。callback(newValue, oldValue, onCleanup):数据变化时执行的回调。onCleanup用于注册清理副作用函数。options:{ deep: true, immediate: true, flush: 'post' }等。
watch(count, (newVal, oldVal) => { console.log(`Count changed from ${oldVal} to ${newVal}`); }); watch([count, title], ([newCount, newTitle], [oldCount, oldTitle]) => { // 侦听多个源 }); watch(() => state.some.nested.prop, (newVal) => { ... }, { deep: true }); // 深度侦听对象属性watchEffect(effect):- 立即执行传入的函数(
effect),并在其执行过程中自动追踪函数内使用到的所有响应式依赖。当这些依赖任意一个发生变化时,重新运行该函数。 - 更简洁,适用于依赖关系简单或需要立即执行的场景。
- 返回一个停止侦听的函数。
onInvalidate参数注册清理函数(等同于watch的onCleanup)。
const stop = watchEffect((onInvalidate) => { console.log(`Count is: ${count.value}, Title is: ${title.value}`); // 清理副作用 (例如取消请求、清除定时器) onInvalidate(() => { clearTimeout(timerId); }); }); // 稍后停止侦听 stop();- 立即执行传入的函数(
-
生命周期钩子:
- 提供对应的
onX函数在setup()中注册生命周期回调:onBeforeMount/onMountedonBeforeUpdate/onUpdatedonBeforeUnmount/onUnmounted(替代 Vue 2 的beforeDestroy/destroyed)onErrorCapturedonRenderTracked/onRenderTriggered(开发调试)onActivated/onDeactivated(配合keep-alive)onServerPrefetch(SSR)
- 用法: 直接导入并在
setup()中调用。import { onMounted, onUnmounted } from 'vue'; setup() { onMounted(() => { console.log('Component is mounted!'); // 初始化操作,如添加事件监听器、发起请求 }); onUnmounted(() => { console.log('Component is unmounted!'); // 清理操作,如移除事件监听器、取消请求 }); }
- 提供对应的
-
依赖注入 (
provide,inject):- 功能同 Options API 的
provide/inject,但以函数形式提供。 - 祖先组件
provide:import { provide, ref } from 'vue'; setup() { const location = ref('North Pole'); provide('location', location); // 提供响应式数据 provide('updateLocation', (newLoc) => { location.value = newLoc; }); // 提供方法 } - 后代组件
inject:import { inject } from 'vue'; setup() { const location = inject('location'); // 注入值 const updateLocation = inject('updateLocation'); // 注入方法 // 可提供默认值 const nonExistent = inject('key', 'default value'); return { location, updateLocation }; }
- 功能同 Options API 的
Composition API 的核心优势:
- 逻辑复用与封装: 通过 Composables,可以将任意复杂的、与组件无关的逻辑(如数据获取、鼠标跟踪、表单验证、状态管理等)轻松提取、复用和组合。这是 Mixins 难以企及的(命名冲突、来源不清晰)。
- 代码组织更灵活: 基于逻辑功能而非选项类型来组织代码。所有与一个特定功能相关的代码(数据、计算、方法、watch、生命周期)都聚集在
setup()的同一区域,代码可读性和可维护性显著提升,尤其是在大型复杂组件中。 - 更小的生产包 & Tree-Shaking: Composition API 的函数是按需导入的。未使用的 API(如
watchEffect)可以被现代打包工具(如 Webpack、Vite、Rollup)Tree-Shake(摇树优化)掉,减小最终打包体积。Options API 的所有选项都是声明式的,难以被 Tree-Shake。 - 更好的 TypeScript 支持: 函数式 API 对类型推导非常友好。变量、函数参数和返回值的类型可以清晰地定义和推断,大大提升了使用 TypeScript 开发 Vue 应用的体验。
- 更少的
this上下文问题: 在setup()中完全避免了this,代码更纯粹,逻辑更易于理解和测试(测试时无需模拟组件实例)。
何时选择 Composition API?
- 强烈推荐: 构建大型应用、复杂组件、需要高度复用逻辑的场景、使用 TypeScript。
- 仍然可用: 小型应用、简单组件、个人偏好 Options API 风格。
总结: Vue 3 的 Composition API 通过提供基于函数的、灵活的逻辑组织方式,彻底解决了 Vue 2 Options API 在复杂组件开发和逻辑复用上的痛点。它赋能开发者编写出更清晰、更模块化、更易复用、类型更安全的代码,是构建现代 Vue 应用的强大工具集。理解 setup(), ref, reactive, computed, watch/watchEffect, 生命周期钩子以及如何构建 Composables 是掌握 Composition API 的关键。