本系列介绍有 Vue2 开发经验的人迁移到 Vue3 应该学习的内容。本文主要讲解:组合 API、setup 语法糖。

组合 API(Composition API)

组合 API 是一种新的编写组件的方式,之前我们写的组件都叫选项 API(Options API),即 export 出一个对象,对象中包含 data、method、created 等等属性。

组合 API 示例如下:

<script setup>
import { ref, onMounted } from 'vue'

// 响应式状态
const count = ref(0)

// 改变状态并触发视图更新
function increment() {
  count.value++
}

// 生命周期钩子
onMounted(() => {
  console.log(`The initial count is ${count.value}.`)
})
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

使用 ref 生成响应式状态,onMounted 等方法调用生命周期。

为什么使用组合 API?

更好的逻辑重用

它可以高效简洁的组合函数,解决了 mixin 的所有缺点,如现在可以拆出 VueUse 这种逻辑重用的库。

更灵活的代码组织方式

官网这张图非常形象:

image.png

不同的颜色代表业务上不同的逻辑,选项 API 时,逻辑比较分散,需要在代码间频繁跳转,组合 API 时逻辑都在一起,符合直觉。

更好的类型推断

基于组合 API 就是函数的天生优势,在 TypeScript 中类型推断更简单方便。

更小的生产包和更少的开销

<script setup> 语法写的代码都在 setup 作用域中,不像选项 API 写的代码都在 this 上,所以 setup 不需要实例的代理,所以更快。

与选项 API 的关系

刚使用组合 API 可能觉得太麻烦了,不如选项 API 好写,但换种方式思考,组合 API 帮助你把代码组织在一起,这使得代码易于重构,虽然选项 API 写起来不用动脑,但有经验证明这在大型项目中不利于维护。

组合 API 是否覆盖了选项 API 的所有用例?

是的。

如果你打算只使用组合 API,不用选项 API,那么还可以通过配置编译选项减少打包体积。

可以同时使用这两个 API 吗?

可以,在选项 API 中使用 setup 方法即可,不建议这样做,除非你想在已有的选项 API 代码中使用组合 API。

选项 API 会被启用吗?

不会。

Composition API 的许多好处只体现在大型项目中,而 Options API 仍然是许多中低复杂度场景的可靠选择。

与 Class API 的关系

Class API 是 Vue2 时使用 TypeScript 的一种方式,在 Vue3 中不推荐使用了,因为 Vue3 使用组合 API 即可更优秀的与 TypeScript 结合。

与 React Hooks 的比较

Composition API 提供与 React Hooks 相同级别的逻辑组合功能,但有一些更好的优化。

我们承认 React Hooks 的创造力,它是 Composition API 的主要灵感来源。然而,它的设计中确实存在某些问题,Vue 恰好提供了解决这些问题的方案。

<script setup> 语法糖

<script setup> 是在 Single-File Components (SFCs) 中使用组合 API 的编译时语法糖,它有很多优点:

  • 更少的模板代码
  • TypeScript 支持声明 props 和 emit 事件
  • 更优的运行时性能
  • 更优的 IDE 类型推断性能

基本语法

<script setup>
console.log('hello script setup')
</script>

script 标签加上 setup 属性,它会在每次组件实例创建时执行,而纯 script 标签只会在被引用时执行一次。

顶层变量默认暴露到 template

<script setup>
// variable
const msg = 'Hello!'

// functions
function log() {
  console.log(msg)
}
</script>

<template>
  <button @click="log">{{ msg }}</button>
</template>

不需要 export 出 msg 和 log,在 template 中直接使用即可,真的减少了很多模板代码。

甚至可以直接引用变量与组件

<script setup>
import { capitalize } from './helpers'
</script>

<template>
  <div>{{ capitalize('hello') }}</div>
</template>
<script setup>
import MyComponent from './MyComponent.vue'
</script>

<template>
  <MyComponent />
</template>

defineProps() & defineEmits()

用于定义 props  和  emits

//父组件
<template>
  <Tab
      :page="page"    //传递值
      @pageFn="pageFn"  //传递方法
  ></Tab>
</template>

<script setup>
import Tab from "./Tab.vue";
import { ref } from "vue";

const page=ref(1)
const pageFn=(val)=>{
    page.value=val
}
</script>

//子组件
<template>
  <button @click="butFn">改变page值:{{page}}</button>
</template>

<script setup>
defineProps(["page"]);  //接收父组件传来的值
const emit = defineEmits(["pageFn"]);   //定义一个变量来接收父组件传来的方法

const butFn=()=>{
    // 触发父组件中的 pageFn 方法,并传入参数5
    emit("pageFn",5)
}
</script>

注意:defineProps、defineEmits 等在 setup 中是宏,所以可直接调用,不需要 import。

defineExpose()

// 父组件
<template>
  <Child ref="RefChildExpose"></Child>
  <button @click="touchButton">点击使用子组件</button>
</template>
<script setup>
const RefChildExpose = ref(null)
function touchButton () {
  // 使用子组件方法
  RefChildExpose.value.show()
  // 输出子组件属性
  console.log(RefChildExpose.value.count)
}
</script>

// 子组件
<script setup>
function show () {
  console.log('显示')
}
defineExpose({
  show,
  count: 1
})
</script>

子组件的 ref 被调用时,就需要声明在这里。

忍不住点赞,Vue3 终于解决了 emit 和 ref 的声明,还删掉了 mixin,我在 Vue2 时总是苦于找不到函数定义或函数是否被其他组件使用,现在不用担心了。

defineOptions()

当你使用 setup 语法糖开发时,可能还需要选项 API 的配置项,如inheritAttrs属性,这时你可以使用 defineOptions 宏,避免再多写一个普通  <script> 标签。

useSlots() & useAttrs()

在  <script setup>  中几乎不会使用  slots  和  attrs,因为可以在模板中直接使用  $slots  和  $attrs  访问它们。但在极少数情况下可能确实需要,请使用  useSlots  和  useAttrs  来获取。

<script setup>
import { useSlots, useAttrs } from 'vue'

const slots = useSlots()
const attrs = useAttrs()
</script>

与普通 <script> 标签一起使用

<script>
// normal <script>, executed in module scope (only once)
runSideEffectOnce()

// declare additional options
export default {
  inheritAttrs: false,
  customOptions: {}
}
</script>

<script setup>
// executed in setup() scope (for each instance)
</script>

少数场景下, <script setup>不能满足需求时,可以这样写,但请注意:

  1. 不要在 普通 <script>中重复定义  <script setup>中已有的内容,如props  和  emits 。
  2. 在  <script setup>  中创建的变量不会作为属性添加到组件实例中,因此无法从 选项 API 中访问它们。强烈建议不要混合这两种 API 的状态变量。

如果以上方法还是不能满足需求,那么你应该使用 setup 函数,而不是 <script setup>

顶层 await

顶层  await  可以在  <script setup>  中使用。生成的代码将被编译为  async setup() :

<script setup>
const post = await fetch(`/api/post/1`).then((r) => r.json())
</script>

注意:async setup()  必须与  Suspense  结合使用。

参考:

  1. Vue3 迁移文档
  2. 十分钟,带你了解 Vue3 的新写法