组合

mixins

Mixin 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个 mixin 对象可以包含任意组件选项。当组件使用 mixin 对象时,所有 mixin 对象的选项将被“混合”进入该组件本身的选项。

当组件和 mixin 对象含有同名选项时,这些选项将以恰当的方式进行“合并”。

比如,每个 mixin 可以拥有自己的 data 函数。每个 data 函数都会被调用,并将返回结果合并。在数据的 property 发生冲突时,会以组件自身的数据为优先。

同名钩子函数将合并为一个数组,因此都将被调用。另外,mixin 对象的钩子将在组件自身钩子之前调用。

值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

不足

在 Vue 2 中,mixin 是将部分组件逻辑抽象成可重用块的主要工具。但是,他们有几个问题:

  • Mixin 很容易发生冲突:因为每个 mixin 的 property 都被合并到同一个组件中,所以为了避免 property 名冲突,你仍然需要了解其他每个特性。
  • 可重用性是有限的:我们不能向 mixin 传递任何参数来改变它的逻辑,这降低了它们在抽象逻辑方面的灵活性。

为了解决这些问题,我们添加了一种通过逻辑关注点组织代码的新方法:组合式 API

extends

允许一个组件扩展到另一个组件,且继承该组件选项。

从实现的角度看,extends 几乎等同于 mixins。可以认为其作为第一个 mixin 作用在被 extends 的组件上。 然而,extends 和 mixins 表达了不同的意图。mixins 选项主要用来组合功能,而 extends 主要用来考虑继承性。 和 mixins 类似,任何选项都会通过对应的合并策略被合并。

  1. const CompA = { ... }
  2. const CompB = {
  3. extends: CompA,
  4. ...
  5. }

provide / inject

provide 和 inject 启用依赖注入。这两者只能在使用当前活动实例的 setup() 期间被调用。

使用 provide

在 setup() 中使用 provide 时,我们首先从 vue 显式导入 provide 方法。这使我们能够调用 provide 来定义每个 property。
provide 函数允许你通过两个参数定义 property:

  1. name ( 类型)
  2. value

    使用 inject

    在 setup() 中使用 inject 时,也需要从 vue 显式导入。导入以后,我们就可以调用它来定义暴露给我们的组件方式。
    inject 函数有两个参数:

  3. 要 inject 的 property 的 name

  4. 默认值 (可选)

    响应性

    添加响应性

    为了增加 provide 值和 inject 值之间的响应性,我们可以在 provide 值时使用 refreactive

    修改响应式 property

    当使用响应式 provide / inject 值时,建议尽可能将对响应式 property 的所有修改限制在定义 provide 的组件内部

最后,如果要确保通过 provide 传递的数据不会被 inject 的组件更改,我们建议对提供者的 property 使用 readonly。

setup

Why

使用 (data、computed、methods、watch) 组件选项来组织逻辑通常都很有效。然而,当我们的组件开始变得更大时,逻辑关注点的列表也会增长。尤其对于那些一开始没有编写这些组件的人来说,这会导致组件难以阅读和理解。

这种碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块

如果能够将同一个逻辑关注点相关代码收集在一起会更好。而这正是组合式 API 使我们能够做到的。

组合式API基础(done)

为了开始使用组合式 API,我们首先需要一个可以实际使用它的地方。在 Vue 组件中,我们将此位置称为 setup。

新的 setup 选项在组件创建之前执行,一旦 props 被解析,就将作为组合式 API 的入口。

在 setup 中你应该避免使用 this,因为它不会找到组件实例。setup 的调用发生在 data property、computed property 或 methods 被解析之前,所以它们无法在 setup 中被获取。

返回值

setup 选项是一个接收 props 和 context 的函数

返回对象

此外,我们将 setup 返回的所有内容都暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板

  1. <template>
  2. <div>{{ collectionName }}: {{ readersNumber }} {{ book.title }}</div>
  3. </template>
  4. <script>
  5. import { ref, reactive } from 'vue'
  6. export default {
  7. props: {
  8. collectionName: String
  9. },
  10. setup(props) {
  11. const readersNumber = ref(0)
  12. const book = reactive({ title: 'Vue 3 Guide' })
  13. return {
  14. readersNumber,
  15. book
  16. }
  17. }
  18. }
  19. </script>

注意,从 setup 返回的 refs 在模板中访问时是被自动浅解包的,因此不应在模板中使用 .value。

返回一个渲染函数

setup 还可以返回一个渲染函数,该函数可以直接使用在同一作用域中声明的响应式状态:

  1. import { h, ref, reactive } from 'vue'
  2. export default {
  3. setup() {
  4. const readersNumber = ref(0)
  5. const book = reactive({ title: 'Vue 3 Guide' })
  6. // 请注意这里我们需要显式调用 ref 的 value
  7. return () => h('div', [readersNumber.value, book.title])
  8. }
  9. }

参数

使用 setup 函数时,它将接收两个参数:

  1. props
  2. context

    Props

    setup 函数中的第一个参数是 props。正如在一个标准组件中所期望的那样,setup 函数中的 props 是响应式的,当传入新的 prop 时,它将被更新。

    因为 props 是响应式的,你不能使用 ES6 解构,它会消除 prop 的响应性。

如果需要解构 prop,可以在 setup 函数中使用 toRefs 函数来完成此操作:

  1. import { toRefs } from 'vue'
  2. setup(props) {
  3. const { title } = toRefs(props)
  4. console.log(title.value)
  5. }

如果 title 是可选的 prop,则传入的 props 中可能没有 title 。在这种情况下,toRefs 将不会为 title 创建一个 ref 。你需要使用 toRef 替代它:

  1. import { toRef } from 'vue'
  2. setup(props) {
  3. const title = toRef(props, 'title')
  4. console.log(title.value)
  5. }

Context

传递给 setup 函数的第二个参数是 context。context 是一个普通的 JavaScript 对象,它暴露组件的三个 property:

  1. export default {
  2. setup(props, context) {
  3. // Attribute (非响应式对象)
  4. console.log(context.attrs)
  5. // 插槽 (非响应式对象)
  6. console.log(context.slots)
  7. // 触发事件 (方法)
  8. console.log(context.emit)
  9. }
  10. }

context 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对 context 使用 ES6 解构。

  1. export default {
  2. setup(props, { attrs, slots, emit }) {
  3. ...
  4. }
  5. }

attrs 和 slots 是有状态的对象,它们总是会随组件本身的更新而更新。这意味着你应该避免对它们进行解构,并始终以 attrs.x 或 slots.x 的方式引用 property。请注意,与 props 不同,attrs 和 slots 是响应式的。如果你打算根据 attrs 或 slots 更改应用副作用,那么应该在 onUpdated 生命周期钩子中执行此操作。

访问 组件的 property

执行 setup 时,组件实例尚未被创建。因此,你只能访问以下 property:

  • props
  • attrs
  • slots
  • emit

换句话说,你将无法访问以下组件选项:

  • data
  • computed
  • methods

在 setup 内注册生命周期钩子

为了使组合式 API 的功能和选项式 API 一样完整,我们还需要一种在 setup 中注册生命周期钩子的方法。这要归功于 Vue 导出的几个新函数。组合式 API 上的生命周期钩子与选项式 API 的名称相同,但前缀为 on:即 mounted 看起来会像 onMounted。

这些函数接受一个回调,当钩子被组件调用时,该回调将被执行。

选项式 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

因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。

映射关系

  • beforeCreate -> 使用 setup()
  • created -> 使用 setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeUnmount -> onBeforeUnmount
  • unmounted -> onUnmounted
  • errorCaptured -> onErrorCaptured
  • renderTracked -> onRenderTracked
  • renderTriggered -> onRenderTriggered
  • activated -> onActivated
  • deactivated -> onDeactivated

    使用this

    在 setup() 内部,this 不是该活跃实例的引用,因为 setup() 是在解析其它组件选项之前被调用的,所以 setup() 内部的 this 的行为与其它选项中的 this 完全不同。这使得 setup() 在和其它选项式 API 一起使用时可能会导致混淆。