# composition API

提示

在 Vue 3 中,composition API (组合式 API) 几乎无处不在

这也是比较推荐的使用方式。

这里将会有大量篇幅来介绍组合式 API 相关内容,如果你是 Vue 3 的新手开发者,请详细阅读本篇章

# 什么是组合式 API

compositionAPI 组合式 API 是 Vue 3 引入的一种编码方式,在 Vue 2 的开发中,我们将逻辑固定死data中只能放数据,methods中只能放函数等等,过大的逻辑处理流程将会在templatedatamethods中跳来跳去,其实是非常不利于开发的,而通过组合式 API 的方式来处理逻辑,将会增加复用性、解耦性,掌握后对于开发效率会有不小的提高。

通过查看 官方文档 (opens new window) 获取更多有关什么是组合式 API 的相关信息

# setup 选项

setup() {}

所有有关组合式 API 的使用,请在setup中使用

# ref

查看 官方文档 (opens new window) 获取所有有关ref的 API

注意

ref 函数只能接收基本数据类型的参数,例如0'aa'[]等,无法接收 Object 对象,创建响应式对象请使用下文的reactive

通过ref来创建一个响应式变量

例如:

let counter = ref(0)
1

ref函数接收一个参数,该参数就是响应式变量的初始值

上述代码创建一个0的响应式副本counter,是一个对象,只有一个属性value,访问/修改请使用counter.value

  • 逻辑中使用counter.value来读取/写入值
  • 视图中使用counter来读取值

# Vue 2 对照

在 Vue 2 中,上述代码相当于在data中创建了一个属性counter初始值0

export default {
  data() {
    return {
      counter: 0,
    }
  },
}
1
2
3
4
5
6
7

# 例子

import { createApp, ref } from 'vue'

const app = {
  setup() {
    const counter = ref(0)
    function incrementCounter() {
      counter.value++
    }
    return {
      counter,
      incrementCounter,
    }
  },
}

createApp(app).mount('#app')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id="app">
  <button @click="incrementCounter">点击自增 counter 的值</button>
  <div>
    counter's value is {{counter}}
  </div>
</div>
1
2
3
4
5
6

CodeSandbox 链接 (opens new window)

# reactive

通过reactive来创建一个对象的响应式副本,转换是深层次的,基于Proxy实现,转换后的对象不等于原来的对象

查看 官方文档 (opens new window) 获取更多有关 reactive 内容

let fetchUserInfo = reactive({
  name: 'alex',
  age: 18,
  address: 'Beijing',
})
1
2
3
4
5

CodeSandbox 例子 (opens new window)

# Vue 2 对照

上述代码相当于在 data 中创建

export default {
  data() {
    return {
      fetchUserInfo: {
        name: 'alex',
        age: 18,
        address: 'Beijing',
      },
    }
  },
}
1
2
3
4
5
6
7
8
9
10
11

# 生命周期

setup中注册生命周期钩子函数,类似于以下:

import { onBeforeCreate } from 'vue'
export default {
  setup() {
    onBeforeCreate(() => {
      console.log('触发了 beforeCreate 生命周期')
    })
  },
}
1
2
3
4
5
6
7
8

# 和 options API 对照

注意

setup 是围绕着beforeCreatecreated这两个生命周期钩子的,因此在setup中不需要使用。推荐将这两个钩子函数的代码写在setup

查看 官方文档 (opens new window) 获取更多对照信息

一模一样,只是增加了on前缀

  • beforeCreateonBeforeCreate
  • createdonCreated
  • beforeMountonBeforeMounted
  • mountedonMounted
  • beforeUpdateonBeforeUpdate
  • updatedonBeforeUpdated
  • beforeUnmountonBeforeUnmount
  • unmountedonUnmounted

例子请看:

CodeSandbox 链接 (opens new window)

# computed

  • 传入参数,如果是一个函数,那么就是 getter,返回一个值不可变的 ref 变量(通过.value来访问值)
import { ref, computed } from 'vue'
export default {
  setup() {
    const counter = ref(2)
    // computed 第一个参数接收一个 getter 函数
    const doubleCounter = computed(() => counter.value * 2)
    // 如果想要 getter/setter,需要将第一个参数修改为一个对象
    let trebleCounter = computed({
      get() {
        return counter.value * 3
      },
      set(val) {
        counter.value = val / 3
      },
    })
    function decrementTrebleCounter() {
      // 访问通过`.value`来访问
      trebleCounter.value = trebleCounter.value - 1
    }
  },
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

CodeSandbox 地址 (opens new window)

# watch

查看 官方文档 (opens new window) 获取更多有关 watch 的内容

接收 3 个参数:

  • 一个想要侦听的响应式数据或者 getter 函数
  • 一个回调
  • 可选的配置
const app = {
  setup() {
    const fetchUserInfo = reactive({
      username: 'alex',
      age: 18,
    })
    watch(
      fetchUserInfo,
      (val, oldVal) => {
        console.log('监听到 fetchUserInfo 发生变化', val)
      },
      {
        deep: true,
      }
    )
    function changeUsername(val) {
      fetchUserInfo.username = val
    }
    return {
      fetchUserInfo,
      changeUsername,
    }
  },
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

CodeSandbox 地址 (opens new window)

# Vue 2 对照

export default {
  data() {
    return {
      fetchUserInfo: {
        username: 'alex',
        age: 18,
      },
    }
  },
  watch: {
    fetchUserInfo: {
      deep: true,
      handler(val) {
        console.log('监听到 fetchUserInfo 发生变化', val)
      },
    },
  },
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 为什么需要组合式 API

学习了组合式 API 部分,相信你还是有疑问,为什么需要组合式 API?将原本datamethods等等所有写在setup里,不是还是非常臃肿吗?组合式 API 十分接近纯函数的概念,和react hook的开发体验很相似,正如指南最开始所说,composition API将代码大大提高了可复用性和解耦性

# 一个例子

我们试试使用options创建一个稍微复杂一点的 todo 案例

查看代码:CodeSandbox 地址 (opens new window)

试想以下,如果要多个地方用到todo,是不是要重写一遍?你可能会想封装为组件,但是如果其他地方的样式稍微变化了一下呢?我们仅仅是需要所有有关 todo 的逻辑,而页面则是自己写,那么组合式 API 就是一个很棒的解决方案

组合式 API 改造后的代码:CodeSandbox 地址 (opens new window)

通过useTodo这个hook,我们可以获取到暴露出来的属性和方法,而无需再关心其内部逻辑,再次使用,只需要引入其属性即可,真正做到了解耦

# 在单文件组件中使用 setup

Vue 3.2.x版本正式通过这个setup语法糖提案,我们在使用组合式 API 的时候,必须要使用setup(){},并且需要return才能在模板中使用,但是在单文件组件中,可以直接这样

<template> counter is {{ counter }} </template>
<script setup>
  import { ref } from 'vue'
  let counter = ref(0)
</script>
1
2
3
4
5

script中使用setup关键字,无需setup(){},无需export default

# 一些规范

查看 官方文档 (opens new window) 获取更多有关编码风格的内容

以下是一些编码风格约定

  • 在创建可组合式函数(hook)时,最好使用useXxx,以use开头

# 写在最后

关于组合式 API,本指南只说这么多,但是旅途还远远没有结束,Vue 3 的官方文档写的相当详细,我也希望你能够通读一遍文档。

组合式 API 和选项式 API 的逻辑与表现大大不同,过于灵活的组合式 API 肯定一开始是不适应的,希望你能够摒弃掉 options API 的思想,全面拥抱 composition API。