1. 核心概念与用法
Vue 3 的 Composition API 引入了一套全新的、基于函数的 API,旨在解决在构建大型复杂应用时,Options API 所面临的代码组织困难和逻辑复用性差的痛点。它并非要完全取代 Options API,而是提供了一种更灵活、更强大的替代方案,让开发者能够根据逻辑关注点来组织代码,而不是被限制在 data
、methods
、computed
等固定的选项中 。这套 API 的核心思想是将组件的逻辑拆分成更小、更独立的函数,这些函数可以根据需要自由组合,从而极大地提升了代码的可读性、可维护性和复用性 。Composition API 主要包含响应式 API(如 ref
和 reactive
)、生命周期钩子(如 onMounted
)以及依赖注入(如 provide
和 inject
)等,它们共同构成了一个功能强大的工具集,使开发者能够以更函数式、更模块化的方式来构建 Vue 组件 。
1.1 setup()
函数:Composition API 的入口
setup()
函数是 Composition API 的核心和入口点。它在组件实例被创建之前执行,是组织组件逻辑的主要场所。与 Options API 中逻辑分散在各个选项(data
, methods
, computed
等)不同,setup()
允许我们将相关的状态、方法、计算属性等逻辑集中在一起,按照功能模块进行组织,从而解决了复杂组件中逻辑分散、难以追踪的问题 。这个函数接收两个参数:props
和 context
。props
是一个响应式对象,包含了父组件传递过来的所有属性,当父组件的 props
发生变化时,setup
函数会重新执行以获取最新的值 。context
是一个普通的 JavaScript 对象,它暴露了 attrs
、slots
和 emit
等组件上下文信息,这些在 Vue 2 中通过 this
访问的属性,在 setup
中需要通过 context
来访问,因为 setup
执行时组件实例尚未创建,this
并不可用 。
setup()
函数的返回值决定了模板中可以访问的变量和方法。任何在 setup
中定义并返回的对象、函数或响应式数据,都可以直接在组件的模板中使用。例如,我们可以返回一个包含响应式数据和方法的对象,模板就可以通过插值语法或事件绑定来直接使用它们 。这种显式的返回机制使得组件的 API 更加清晰,开发者可以一目了然地知道哪些状态和行为是暴露给模板使用的。此外,Vue 3 还引入了 <script setup>
语法糖,它简化了 setup
函数的写法,使得在单文件组件中使用 Composition API 变得更加简洁和直观。在 <script setup>
中,所有顶层定义的变量和函数都会自动暴露给模板,无需手动返回,进一步提升了开发效率 。
1.1.1 setup()
的执行时机与参数
setup()
函数的执行时机非常早,它在组件生命周期的 beforeCreate
钩子之前被调用,此时组件实例尚未完全创建,因此在 setup
内部无法访问 this
上下文 。这个函数接收两个主要参数:props
和 context
,并返回一个对象,该对象中的属性和方法将被暴露给组件的模板(template)使用 。
-
props
: 这是一个响应式的对象,包含了父组件传递给当前组件的所有属性。与 Vue 2 不同,这里的props
是只读的,直接修改它不会触发更新,并且会在开发模式下收到警告。由于props
是响应式的,你可以使用watch
或watchEffect
来监听其变化 。 -
context
: 这是一个普通的 JavaScript 对象,暴露了三个组件的 property:attrs
: 包含了所有未在props
中声明的属性(即非 prop 的 attribute)。与props
不同,attrs
不是响应式的。slots
: 一个插槽函数的对象,可以用来访问插槽内容。emit
: 一个函数,用于触发自定义事件,向父组件传递数据。它等价于 Options API 中的this.$emit
。
一个完整的 setup
函数签名示例如下:
import {
defineComponent } from 'vue';
export default defineComponent({
props: {
message: String
},
setup(props, {
attrs, slots, emit }) {
console.log('Props:', props.message);
console.log('Attrs:', attrs);
console.log('Slots:', slots);
console.log('Emit function:', emit);
// ... 定义响应式数据、方法等
return {
// 返回要暴露给模板的属性和方法
};
}
});
通过这种方式,setup
函数为组件的逻辑提供了一个清晰、独立的入口点,使得代码组织更加模块化 。
1.1.2 setup()
的返回值与模板访问
setup()
函数必须返回一个对象,这个返回的对象中的属性(包括响应式数据、计算属性、方法等)将会被合并到组件的渲染上下文中,从而可以在模板中直接使用。这是连接 Composition API 逻辑与组件视图的桥梁 。例如,如果你在 setup
中定义了一个 ref
变量 count
和一个方法 increment
,你需要在返回的对象中显式地包含它们,模板才能访问 {
{ count }}
和 @click="increment"
。
除了返回一个对象,setup
函数还可以返回一个渲染函数(render function)。当返回渲染函数时,该函数将直接用于渲染组件的 DOM,此时不能再返回其他属性。这种方式提供了对渲染过程的完全控制,适用于需要高度动态渲染的场景。如果在这种情况下仍然需要向父组件暴露一些方法(例如,通过模板 ref 调用),可以使用 context
参数中的 expose
函数。expose
接收一个对象,该对象中定义的属性将被显式地暴露给组件实例,即使 setup
返回的是一个渲染函数 。
import {
h, ref } from 'vue';
export default {
setup(props, {
expose }) {
const count = ref(0);
const increment = () => count.value++;
// 暴露 increment 方法给父组件
expose({
increment
});
// 返回一个渲染函数
return () => h('div', count.value);
}
};
这种灵活的返回机制使得 setup
函数既能满足常规的模板驱动开发,也能应对更复杂的、需要程序化控制渲染的需求。
1.2 响应式 API:ref
与 reactive
Vue 3 的响应式系统是其核心特性之一,而 ref
和 reactive
是 Composition API 中创建响应式数据的两个主要工具。它们都基于 ES6 的 Proxy
对象实现,能够拦截对数据的访问和修改,从而实现依赖收集和自动更新视图 。尽管它们的目标相似,但在使用方式、适用场景和行为上存在显著差异。
1.2.1 ref
:处理基本类型与引用类型
ref
函数用于创建一个响应式的引用(reference)。它可以将任何类型的值(包括基本类型如 string
, number
, boolean
,以及引用类型如 object
, array
)包装成一个带有 .value
属性的响应式对象 。当访问或修改 .value
时,Vue 的响应式系统会进行追踪和触发更新。
- 基本类型: 对于基本类型,
ref
是实现响应式的唯一选择,因为reactive
只能处理对象。 - 引用类型:
ref
也可以处理对象和数组。当ref
的值是一个对象时,Vue 会通过reactive
自动将其转换为深层响应式代理 。这意味着你可以直接修改嵌套对象的属性,并且这些修改会被检测到。
在 JavaScript 代码中,必须通过 .value
来访问或修改 ref
所包装的值。然而,在模板中,Vue 会自动“解包” ref
,因此可以直接使用变量名,无需 .value
。
import {
ref } from 'vue';
const count = ref(0); // 基本类型
const state = ref({
name: 'Vue' }); // 引用类型
console.log(count.value); // 0
count.value++;
console.log(state.value.name); // 'Vue'
state.value.name = 'Vue 3';
ref
的一个重要优势是,当你重新为 .value
赋一个新对象时,其响应性不会丢失,这与 reactive
的行为不同 。
1.2.2 reactive
:处理复杂对象
reactive
函数用于将一个普通对象转换为一个深层响应式的代理对象。与 ref
不同,reactive
直接作用于对象本身,返回的代理对象与原始对象不相等 。对代理对象的任何属性(包括嵌套属性)的修改都会被 Vue 的响应式系统追踪。
reactive
非常适合用于管理复杂的状态对象,例如一个包含多个嵌套属性和数组的表单数据。它默认是深层响应的,这意味着对象内部的所有嵌套对象和数组都会被递归地转换为响应式代理 。
import {
reactive } from 'vue';
const formState = reactive({
user: {
name: '',
age: null
},
preferences: ['reading', 'coding']
});
// 直接修改属性
formState.user.name =