Vue3 组合式API
什么是组合式 API?
使用 (data
、computed
、methods
、watch
) 组件选项来组织逻辑通常都很有效。
然而,当我们的组件开始变得更大时,逻辑关注点的列表也会增长。尤其对于那些一开始没有编写这些组件的人来说,这会导致组件难以阅读和理解。
这种碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。
如果能够将同一个逻辑关注点相关代码收集在一起会更好。而这正是组合式 API 使我们能够做到的。
组合式 API 基础
setup
组件选项
新的 setup
选项在组件创建之前执行,一旦 props
被解析,就将作为组合式 API 的入口。
TIP
在
setup
中你应该避免使用this
,因为它不会找到组件实例。setup
的调用发生在data
property、computed
property 或methods
被解析之前,所以它们无法在setup
中被获取。
setup
选项是一个接收 props
和 context
的函数。
此外,我们将 setup
返回的所有内容都暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板。
让我们把 setup
添加到组件中
1 | // src/components/UserRepositories.vue |
带 ref
的响应式变量
在 Vue 3.0 中,我们可以通过一个新的 ref
函数使任何响应式变量在任何地方起作用,如下所示:
1 | import { ref } from 'vue' |
ref
接收参数并将其包裹在一个带有 value
property 的对象中返回,然后可以使用该 property 访问或更改响应式变量的值:
1 | import { ref } from 'vue' |
将值封装在一个对象中,看似没有必要,但为了保持 JavaScript 中不同数据类型的行为统一,这是必须的。
这是因为在 JavaScript 中,Number
或 String
等基本类型是通过值而非引用传递的:
在任何值周围都有一个封装对象,这样我们就可以在整个应用中安全地传递它,而不必担心在某个地方失去它的响应性。
TIP
换句话说,
ref
为我们的值创建了一个响应式引用。在整个组合式 API 中会经常使用引用的概念。
在 setup
内注册生命周期钩子
为了使组合式 API 的功能和选项式 API 一样完整,我们还需要一种在 setup
中注册生命周期钩子的方法。
组合式 API 上的生命周期钩子与选项式 API 的名称相同,但前缀为 on
:即 mounted
看起来会像 onMounted
。
这些函数接受一个回调,当钩子被组件调用时,该回调将被执行。
watch
响应式更改
就像我们在组件中使用 watch
选项并在 user
property 上设置侦听器一样,我们也可以使用从 Vue 导入的 watch
函数执行相同的操作。它接受 3 个参数:
- 一个想要侦听的响应式引用或 getter 函数
- 一个回调
- 可选的配置选项
1 | import { ref, watch } from 'vue' |
每当 counter
被修改时,例如 counter.value=5
,侦听将触发并执行回调 (第二个参数),在本例中,它将把 'The new counter value is:5'
记录到控制台中。
以下是等效的选项式 API:
1 | export default { |
有关 watch
的详细信息,请参阅我们的深入指南。
1 | // src/components/UserRepositories.vue `setup` function |
你可能已经注意到在我们的 setup
的顶部使用了 toRefs
。这是为了确保我们的侦听器能够根据 user
prop 的变化做出反应。
独立的 computed
属性
与 ref
和 watch
类似,也可以使用从 Vue 导入的 computed
函数在 Vue 组件外部创建计算属性。
1 | import { ref, computed } from 'vue' |
这里我们给 computed
函数传递了第一个参数,它是一个类似 getter 的回调函数,输出的是一个只读的响应式引用。
为了访问新创建的计算变量的 value,我们需要像 ref
一样使用 .value
property。
对于其他的逻辑关注点我们也可以这样做,但是你可能已经在问这个问题了——这不就是把代码移到 setup
选项并使它变得非常大吗?嗯,确实是这样的。这就是为什么我们要在继续其他任务之前,我们首先要将上述代码提取到一个独立的组合式函数中。
Setup
参数
使用 setup
函数时,它将接收两个参数:
- `props``
- ``context`
Props
setup
函数中的第一个参数是 props
。正如在一个标准组件中所期望的那样,setup
函数中的 props
是响应式的,当传入新的 prop 时,它将被更新。
1 | // MyBook.vue |
TIP
props
是响应式的,你不能使用 ES6 解构,它会消除 prop 的响应性。
1 | const { createApp, defineAsyncComponent } = Vue |
1 | // MyBook.vue |
如果 title
是可选的 prop,则传入的 props
中可能没有 title
。在这种情况下,toRefs
将不会为 title
创建一个 ref 。你需要使用 toRef
替代它:
1 | // MyBook.vue |
Context
context
是一个普通 JavaScript 对象,暴露了其它可能在 setup
中有用的值:
1 | // MyBook.vue |
context
是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对 context
使用 ES6 解构。
1 | // MyBook.vue |
attrs
和 slots
是有状态的对象,它们总是会随组件本身的更新而更新。这意味着你应该避免对它们进行解构,并始终以 attrs.x
或 slots.x
的方式引用 property。
attrs
和 slots
的 property 是非响应式的。如果你打算根据 attrs
或 slots
的更改应用副作用,那么应该在 onBeforeUpdate
生命周期钩子中执行此操作。
访问组件的 property
执行 setup
时,你只能访问以下 property:
props
attrs
slots
emit
换句话说,你将无法访问以下组件选项:
data
computed
methods
refs
(模板 ref)
结合模板使用
如果 setup
返回一个对象,那么该对象的 property 以及传递给 setup
的 props
参数中的 property 就都可以在模板中访问到:
1 | <!-- MyBook.vue --> |
注意,从 setup
返回的 refs 在模板中访问时是被自动浅解包的,因此不应在模板中使用 .value
。
使用渲染函数
setup
还可以返回一个渲染函数,该函数可以直接使用在同一作用域中声明的响应式状态:
1 | import { h, ref, reactive } from 'vue' |
返回一个渲染函数将阻止我们返回任何其它的东西。从内部来说这不应该成为一个问题,但当我们想要将这个组件的方法通过模板 ref 暴露给父组件时就不一样了。
我们可以通过调用 expose
来解决这个问题,给它传递一个对象,其中定义的 property 将可以被外部组件实例访问:
1 | import { h, ref } from 'vue' |
increment
方法现在将可以通过父组件的模板 ref 访问。
使用 this
在 setup()
内部,this
不是该活跃实例的引用
setup()
是在解析其它组件选项之前被调用的,所以 setup()
内部的 this
的行为与其它选项中的 this
完全不同。这使得 setup()
在和其它选项式 API 一起使用时可能会导致混淆。
生命周期钩子
你可以通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。
选项式 API | Hook inside setup |
---|---|
beforeCreate |
Not needed* |
created |
Not needed* |
beforeMount |
onBeforeMount |
mounted |
onMounted |
beforeUpdate |
onBeforeUpdate |
updated |
onUpdated |
beforeUnmount |
onBeforeUnmount |
unmounted |
onUnmounted |
errorCaptured |
onErrorCaptured |
renderTracked |
onRenderTracked |
renderTriggered |
onRenderTriggered |
activated |
onActivated |
deactivated |
onDeactivated |
这些函数接受一个回调函数,当钩子被组件调用时将会被执行:
1 | // MyBook.vue |
Provide / Inject
我们也可以在组合式 API 中使用 provide/inject。两者都只能在当前活动实例的 setup()
期间调用。