Vue Composition API全面讲解

83 阅读10分钟

Vue的文档可以说已经相当全了。但是在加班是常态的情况下,我们很难有整块的时间去学习,要想把整个Vue文档都看一遍可能都是一件奢侈的事情。Vue3新出的Composition API的文档说明散落在Vue文档的各个角落,虽然也有专门的篇幅讲了Composition API,但是远不够全面。Composition API其中一个重要的特性就是让逻辑关注点聚焦,但是关于它的文档却不怎么聚焦。这篇文章就是聚焦Composition API,让大家能够快速的全面学习新特性。

Options API 和 Composition API

Vue官方解释Composition API是一种API风格,和他并列的是Options API。 Options API是Vue2中定义组件的方式,一个带有若干选项(属性或者方法)的对象,常用的有这些属性和方法:data、methods、mounted,定义在对象中的属性可以在方法中通过this访问,this指向组件的实例,具体代码的例子如下:

Options API版:

<script>
export default {
  // 从data方法返回的对象属性变成了响应式的状态
  // 可通过this访问
  data() {
    return {
      count: 0
    }
  },

  // methods中是函数,会修改状态触发UI更新
  // 它们可以作为模版上绑定的事件处理函数
  methods: {
    increment() {
      this.count++
    }
  },

  // 生命周期钩子会在相应的阶段被调用
  // mounted将在组件mount后被调用
  mounted() {
    console.log(`The initial count is ${this.count}.`)
  }
}
</script>

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

相信有过Vue开发经验的人对以上代码再熟悉不过了。我们用这种方式做了太多的页面了。

Composition API是Vue3中新加的书写组件逻辑的方式。它并没有像Options API那样规定什么逻辑该写在什么地方,而是提供了一系列API给我们来构建组件的逻辑。Composition API总是和<script setup>一起使用。setup属性告诉Vue在编译期要对代码进行特殊处理,以便里面的顶层定义的变量可以供模版访问。示例代码如下: Composition 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>

这两种API风格的内部机制其实是一样的。Options API实际上是基于Composition API之上实现的,它们都能覆盖我们常见的使用场景。 Options API风格围绕着组件实例的概念构建,和基于类的面向对象思维模式一致,而且Options API对初学者比较友好,屏蔽了响应式的细节,各个option像脚手架一样,指导开发人员将代码有组织地写到指定的地方,这样的代码保证了在一定程度上不会一锅烩,只要业务逻辑不是特别复杂,代码就不会太差到哪儿去。 Composition API风格围绕着函数(Composable)构建,函数返回可供模版使用的状态。它是一个更自由的结构,自由的结构带来的好处就是可以更好的组织和复用逻辑。

Composition API的核心概念

抽象、封装

其实计算机软件业一直在做一件事情:抽象、封装、再抽象、再封装,如此循环往复,一层套一层,最终形成有价值的计算机软件给最终用户使用。举几个粗略的例子来理解这段话:

  1. 计算机软件的最底层是硬件驱动程序,抽象封装后形成操作系统,再抽象封装形成各种应用程序。这里每一层的每个细小的方面又可以划分出多个层
  2. Javascript编程语言的运行环境V8由C++编写,而C++又是由汇编语言搞出来的,汇编语言又是由二进制机器码搞出来的

我们不断找出可复用的东西,把它抽象出来,然后封装他们,把他们组织起来,再在这个基础上再次找到可复用的东西,再抽象,再封装,如此反复之后,开发和维护效率变得越来越高。发展到如今,整个计算机的技术体系已经非常庞大,我们大部分人只能工作在其中一两个层而已。如果我们也能在我们工作的这一层找到可复用的点,把它抽象出来,进行封装,使之可复用,我们的生产效率就会提高,价值也就产生了。

Vue其实也是在做一样的事情。当页面需要新增删除或者修改DOM节点时,我们需要大量繁复的代码操作DOM,在各种前端框架出现之前,前端的代码复杂度往往要大过后端。JQuery对DOM访问进行了封装,风靡一时,但是远远不够,后来Angular将数据与DOM直接绑定,操作DOM变成了直接操作数据(状态),这是人机交互领域的一次变革,其实就是把大家都在重复做的那部分工作给抽象出来了,形成了框架。数据与DOM的绑定概念催生出了React、Vue,从2010年左右的那次重大变革之后一直到今天,10多年过去了,人机交互领域一直没有革命性的抽象封装出现。虽然这段时间没有大的创新,小的进步却在持续进行,React Hook就是一系列小的革新中比较重量级的一个抽象封装,而Vue的Composition API的灵感就是来自于React Hook,千万不要觉得Composition API是后来者就认为它是山寨,它去除了React Hook的糟粕,Composition API至少从表面上看是要比React Hook更好用的,对,是更好用,因为React Hook有太多的坑,Composition API也有坑但是相比React Hook则要少不少,至于最终他们两个谁更厉害,就让历史去评判吧,或许永远没有结论。Composition API到底都做了哪些抽象和封装呢?我们下面开始讲解。

之前我们用Options API写一个组件时,一般需要做下面几件事:

  • data中声明组件模版用到的响应式变量,即状态
  • mounted生命周期中对页面进行初始化,获取服务端的各种数据
  • 事件响应函数中响应用户交互
  • 声明定义watch
  • 声明定义计算属性

每一个功能点都需要在这几部分分别填上逻辑。当业务逻辑不复杂时,整个代码应该还是挺清晰的,但是随着业务的发展,业务逻辑变得很复杂时,这就带来些问题:

  • 各个逻辑功能点由于混在一起,容易耦合

  • 单个逻辑功能点分散在各个部分,增加了开发人员识别的难度。试想一下当你接手到这样的代码,需要改一个功能时,你需要从众多代码中找出若干分散但相关的逻辑,然后对他们进行改动,是不是一件让人头疼的事情? 下面的图来自官方文档,图中的组件使用Options API,相同颜色代表相同的逻辑点:

    options-api.png 这张图很像Windows硬盘碎片整理功能里硬盘的文件分布图,文件碎片散落在硬盘的各个不连续的扇区,导致硬盘需要频繁寻址从而导致运行速度变慢。Vue Options API的逻辑点也是像碎片一样散落在各个Options选项中,随着业务复杂度的增加,维护会变得越来越困难。

因此组件内部如果能够按照逻辑功能点来进行模块化,应该是解决复杂组件问题的一个出路。

现成的方案

现成的有三个解决方案可以将组件内部模块化:

  1. 继续分成若干小组件
  2. 将逻辑放入若干renderless component(无渲染组件)
  3. mixin

方案1:继续分成若干小组件可能是可行的办法之一,但是组件UI有可能没有必要或者没法再分割了,即使组件UI分割了,但是单个组件的逻辑依然复杂


方案2:renderless component(无渲染组件),顾名思义,就是这个组件没有UI,只有一些逻辑。这可以解决方案1的UI无法再分割的问题。无渲染组件处理完逻辑之后,将需要返回的数据通过slot的形式返回给父组件。 我们以短信验证码的获取按钮为例: ```javascript // 无渲染组件:CountDown

}

// 父组件 ``` 上面的代码在做一个短信验证码按钮的功能,点击按钮手机会收到短信验证码,并且60秒内无法重复点击按钮获取验证码,倒计时会显示出来,提醒用户如果想重复获取验证码,则需要等待。 从上面的代码可以看到,CountDown组件中并没有UI,在处理数据之后,通过slot的机制返回给父组件的UI使用,父组件可以在slot返回的数据基础上对UI进行深度定制,这很适合UI简单多样,但是逻辑相对复杂的功能。 可以看到Renderless component确实可以做到对组件逻辑的模块化,我们可以把一个逻辑复杂的组件分解成若干的Renderless component。不过Vue官方并不推荐这种形式,一方面这样做会导致组件实例增多从而有可能影响性能,另一方面slot设计的初衷确实不是用来做这个的,这多少会让代码看起来有些奇怪,像是一种hack手法。

方案3:mixin已经是广为人知了,它的缺点也一样广为人知:

  • 属性来源不清
  • 命名冲突
  • 通过共享属性来进行跨mixin的通讯,导致耦合

mixin一般会在框架层提供一些公共功能,但是它一般不是日常开发的常态,平时业务开发时,mixin会写得比较少。

Composition API方案

上面三种方案都不是很理想。现在来看看Composition API是怎么让组件逻辑模块化的。 继续以上面的短信验证码按钮为例:

<script setup>
  import {ref, onMounted, onUnmounted} from 'vue';
  const initialCountDownTime = 60;
  const countDownTime = ref(initialCountDownTime);
  const disabled = ref(false);
  let intervalId;
  onUnmounted(() => clearInterval(intervalId));
  
  function sendVerificationCodeMessage() {
  
    if(disabled) {
      return;
    }
    // 发送请求
    // 开始倒计时
    intervalId = setInterval(() => {
      countDownTime--;
      if(countDownTime === 0) {
        disabled = false;
        countDownTime = initialCountDownTime;
        clearInterval(intervalId);
      }
            
    }, 1000);
    disabled = true;
  }
</script>
<template>
<div>
  <button @click="sendVerificationCodeMessage()"  disabled="{{disabled}}">获取验证码</button>
    {{countDownTime}}
</div>
</template>

和上面的renderless component对比,少了一些模版代码,可是把一堆代码放到一起不是更乱了吗?哪里有模块化? 我们再演进一下上面的代码:

// 组件
<script setup>
import {useVerificationCode} from './verificationCode';
// 顶层变量可以被模版访问
const {countDownTime, disabled, sendVerificationCodeMessage} = useVerificationCode();

</script>
<template>
<div>
  <button @click="sendVerificationCodeMessage()"  disabled="{{disabled}}">获取验证码</button>
    {{countDownTime}}
</div>
</template>

// ./verificationCode文件
import {ref, onMounted, onUnmounted} from 'vue';
export function useVerificationCode() {
  const initialCountDownTime = 60;
  const countDownTime = ref(initialCountDownTime);
  const disabled = ref(false);
  let intervalId;
  onUnmounted(() => clearInterval(intervalId));
  
  function sendVerificationCodeMessage() {
  
    if(disabled) {
      return;
    }
    // 发送请求
    // 开始倒计时
    intervalId = setInterval(() => {
      countDownTime--;
      if(countDownTime === 0) {
        disabled = false;
        countDownTime = initialCountDownTime;
        clearInterval(intervalId);
      }
            
    }, 1000);
    disabled = true;
  }
  return {
    countDownTime,
    disabled,
    sendVerificationCodeMessage
  }
}



可以看到所有关于验证码的逻辑都被放到了useVerificationCode函数中。 组件的逻辑终于模块化了,以后有其他人来修改验证码相关的功能时,他基本可以一眼就看到useVerificationCode,如果只是为了修改验证码的逻辑,直接进useVerificationCode,心无旁骛的修改就可以了。 这里的useVerificationCode被叫做Composable,Vue官方对Composable的定义:Composable是一个函数,该函数利用Composition API封装并重用有状态的逻辑,所谓有状态就是这些数据要被模版访问。 那到底什么是Composition API呢?这里用到的ref、onMounted、onUnmounted就是若干Composition API中的几个。具体都有哪些Composition API可以看下图: image.png 该图来自于英文官方文档,比较可惜的是Vue的中文官方文档要比英文文档落后很多,中文文档就没有这样归类。

从上面的例子可以看出Composable其实就是一个函数,没有特别的地方,通过调用Composition API创建响应式的变量(在Options API中,响应式变量需要提前在data中声明),把响应式变量作为函数返回值返回,Vue的编译器会把<script setup>中的顶层声明的变量编译成模版可访问的变量,因此Composable函数只需要把需要被模版访问的变量返回,然后在<script setup>的顶层声明变量接住Composable的返回值就可以了。

// 组件
<script setup>
import {useVerificationCode} from './verificationCode';
// 顶层变量可以被模版访问
const {countDownTime, disabled, sendVerificationCodeMessage} = useVerificationCode();

</script>
<template>
<div>
  <button @click="sendVerificationCodeMessage()"  disabled="{{disabled}}">获取验证码</button>
    {{countDownTime}}
</div>
</template>

示例代码中,useVerificationCode返回值被解构到变量中,然后模版就可以直接访问了。

因此有了Composition API和<script setup>,我们便可以把状态化的逻辑封装起来,可能大家会想:“我们平时写的函数不也是在封装吗?把函数的返回值赋值给data中声明的变量就可以了,它们有什么区别?”,最大的区别就是Composable可以利用Composition API把生命周期中相关的逻辑、相关的计算属性、相关的watch都放到Composable中,形成逻辑的聚焦,而我们之前写的函数并不能直接去注册组件的生命周期。 之前的Vue做到了组件级别的抽象封装重用,现在终于更进一步,组件中的逻辑也可以抽象出来封装重用了。

下面的图来自官方,图中将Options API和Composition API进行了对比:

composition-api-after.png

可以看到Composition API对逻辑碎片进行了整合,组件逻辑很好的模块化了。

至此大家应该已经了解Composition API是做什么的了,理念还不错,也很简单,实际动手也简单吗?我们的旅程才刚刚开始,下面我们来讲一下具体用法。

Composition API的用法

我们用一张图来描述一下Composition API风格的组件,它的代码结构是什么样子的:

image.png

从图中可以看出代码的业务逻辑部分由之前的对象导出变成了setup,setup由一系列composable组成,上面说过Composable其实并没有什么特别,就是普通函数,Vue编译器不会对它进行任何特殊处理,它只是一个概念。从使用者角度看,Vue3真正带给我们的是Composition API和<script setup>。如果我们不按照图示去组织代码,而是Composable中的逻辑都平铺在setup里,也不是不可以,如果业务逻辑简单则不会有什么问题,但是如果业务逻辑复杂的话,最好还是分成多个Composable。

在你准备切换到Vue3,并准备用VSCode写代码之前,强烈建议先把VSCode的Vue插件Volar装上,同时卸载Vetur,Volar是新一代的插件工具,支持Vue3也支持Vue2,而Vetur只支持Vue2。

<script setup>

<script setup>是单文件组件中开启Composition API风格的开关。它只能使用于单文件组件。这里有不少需要我们了解的东西,咱们一个一个说。

  • 执行时机 每当组件实例被创建时,<script setup>中的代码都会被执行,执行时机是在mounted之前。

  • 模版可访问顶层变量 <script setup>内部顶层的变量、函数声明和导入都可以直接被模版使用,示例代码:

<script setup>
import { capitalize } from './helpers'
// 变量
const msg = 'Hello!'

// 函数声明
function log() {
  console.log(msg)
}
</script>

<template>
  <button @click="log">{{ msg }}</button>
  <div>{{ capitalize('hello') }}</div>
</template>
  • 如何创建响应式变量 在Options API中,在data中声明变量会自动变成响应式变量。在Composition API中,需要我们自己调用Reactive API来包装数据,如果不进行包装,当变量被重新赋值时,模版不会响应。 这里先介绍两个常用的响应式API:

    • ref 接受一个参数,可以是任何类型。 返回一个具有value属性的对象。该对象是响应式的。

      先看如下代码:

        <script setup>
        let commonVariable = 0;
        setInterval(() => {
          commonVariable++;
        }, 1000);
        </script>
        <template>
        <div>
            <!--不会变化-->
            <div>{{commonVariable}}</div>
        </div>
        </template>
      

      可以看到,没有经过ref包装的变量不具有响应变动的能力。 ref既可以包装简单内置类型变量(number、string等),还可以包装对象。

      继续看下面的代码:

        <script setup>
        const reactiveVariable = ref(0);
        setInterval(() => {
            reactiveVariable.value++;
        }, 1000);
        </script>
        <template>
        <div>
            <!--每隔1秒增加1-->
            <div>{{reactiveVariable}}</div>
        </div>
        </template>
      

      经过ref包装后,模版终于可以自动更新了。我们在<script>标签中访问ref对象时,需要使用.value,而在模版中则不需要,模版中可以直接使用ref对象,Vue会自动找到ref对象value属性的值。

      继续看下面的代码:

        <script setup>
        const reactiveVariable = ref(ref(ref(0)));
        setInterval(() => {
            reactiveVariable.value++;
        }, 1000);
        
        </script>
        <template>
        <div>
            <!--每隔1秒增加1-->
            <div>{{reactiveVariable}}</div>
        </div>
        </template>
      

      从上面的代码可以看到,ref经过多次重复调用并不会进行叠加,经过一次ref包装和多次是一样的。

      继续看下面的代码:

        <script setup>
        const reactiveObject = ref({a: {x: 1}});
            setInterval(() => {
            reactiveObject.value.a.x++;
        }, 1000);
        </script>
        <template>
        <div>
            <!--每隔1秒增加1-->
            <div>{{reactiveObject.a.x}}</div>
        </div>
        </template>
      

      当ref的参数是普通对象时,ref会调用reactive方法,将对象深度转换成响应式,对象中任何层级的属性都将是响应式的。如果不想进行深度转换,可以使用shallowRef()。不过要注意的是,转换并不会修改原对象,而给原对象创建了一个代理。

      继续看下面的代码:

        <script setup>s
        const map = new Map();
        map.set("a", 1);
        const reactiveMap = ref(map);
        setInterval(() => {
          reactiveMap.value.set("a", reactiveMap.value.get("a") + 1);
        }, 1000);
        </script>
        <template>
        <div>
            <!--每隔1秒增加1-->
            <div>reactiveMap: {{ reactiveMap.get("a") }}</div>
        </div>
        </template>
      

      可以看到Map经过ref包装后也变成了响应式。

      现在咱们来看一下这个例子:

        <script setup lang="ts">
        import { ref } from "vue";
        // 普通简单类型变量
        let commonVariable = 9;
        setInterval(() => {
          commonVariable++;
        });
        
        // 响应式变量
        const reactiveVariable = ref(0);
        setInterval(() => {
          reactiveVariable.value++;
        }, 1000);
        
        // 普通对象
        const o = { x: 0 };
        setInterval(() => {
          o.x++;
        }, 1000);
        </script>
        <template>
          <div>
            <!--不会变化-->
            <div>{{ commonVariable }}</div>
            <!--每隔1秒增加1-->
            <div>{{ reactiveVariable }}</div>
            <!--每隔1秒增加1-->
            <div>{{ o.x }}</div>
          </div>
        </template>
      

      o对象并没有进行响应式包装,但是似乎模版也会自动更新,其实不然,o.x被更新是因为reactiveVariable被更新触发了重新渲染所致。我们在开发时可能很容易忘记进行响应式包装,而bug并不一定会立即显现,所以需要格外注意。

    • reactive reactive接受一个对象参数,不接受普通简单类型变量。 示例代码如下:

       <script setup lang="ts">
       import { reactive } from "vue";
       const reactiveObject = reactive({ x: 0 });
       setInterval(() => {
         reactiveObject.x++;
       }, 1000);
       </script>
       <template>
       <div>
         <!--每隔1秒增加1-->
         <div>{{ reactiveObject.x }}</div>
        </div>
        </template>
      

      reactive基于proxy机制实现,因此reactive返回的对象和初始对象已经不是一个对象。

      当reactive遇到ref时,某些情况下ref会被自动解包,请看如下示例:

      <script setup lang="ts">
      import { reactive, ref } from "vue";
      const count = ref(0);
      const reactiveObject = reactive({ count });
      // true
      console.log(reactiveObject.count === count.value);
      const anotherRef= ref('a');
      reactiveObject.another = anotherRef;
      // true
      console.log(reactiveObject.another === anotherRef.value);
      </script>
      <template>
        <div>
        </div>
      </template>
      

      可以看到,当ref变量变成reactive对象的属性成员时,即使是将ref赋值给一个新属性,ref都会被自动解包,因为这时确实没有ref存在的意义了。

      但是当对一个ref数组或者map进行reactive响应式包装时,ref不会被自动解包,示例代码如下:

      <script setup lang="ts">
      import { reactive, ref } from "vue";
      const reactiveArray = reactive([ ref('a'), ref('b'), ref('c') ]);
      // 需要.value才能访问
      console.log(reactiveArray[0].value);
      
      const reactiveMap = reactive(new Map([['count', ref(0)]]));
      // 需要.value才能访问
      console.log(reactiveMap.get('count').value);
      </script>
      <template>
        <div>
        </div>
      </template>
      
  • <script setup>中如何使用组件 在Options API中,如果要引入外部组件,需要在components属性中注册一下该组件,才能被使用。Composition API中不需要了,直接import,然后在模版中使用就可以了。 示例代码如下:

    组件文件 ./FooBar.vue

    <script setup>
      import MyComponent from './MyComponent.vue';
      import {FooBar as FooBarAnotherName} from './components';
      import * as Form from './form-components'
    </script>
      
    <template>
      <MyComponent />
      <FooBarAnotherName />
      <Form.Input>
          <Form.Label>label</Form.Label>
      </Form.Input>
    </template>
    

    上面例子有几个知识点:

    • 组件在模版中也可以用连字符(kebab-case)的形式:,但是Vue官方强烈建议使用大驼峰的形式,这样就和变量名称一致了,而且也可以和原生的自定义元素区分开
    • 在模版中组件可以用自己文件名称引用自己,例如本例中文件名称是FooBar,在该组件模版中,可以用引用自己。这在某些场景下还是有些用处的。如果组件名称和该组件引入的其他组件名称冲突,则可以利用import的as语法,给组件起一个别名
    • 例子中的Form是一个组件集合,要想访问某个具体组件,可以使用<Form.*>的形式。这就相当于简单的命名空间
  • 自定义指令 全局自定义指令相比之前的使用方式没有变化,局部自定义指令使用<script setup>后不需要再显式的声明了,直接用对象的形式定义顶层变量便可,指令变量必须遵循vName的命名规则,即首字母必须是v,采用小驼峰命名法,示例如下:

    <script setup>
      const vMyDirective = {
        beforeMount: (el) => {
          // 对元素进行一些操作
        }
      }
    </script>
    <template>
      <h1 v-my-directive>这是标题</h1>
    </template>
    
    

    如果是从其他地方引入的指令,且暴露出的指令名称不符合规则,则可以在import中直接重新命名:

    <script setup>
      import { myDirective as vMyDirective } from './MyDirective.js'
    </script>
    
  • 属性和自定义事件 为了对类型推断进行支持,defineProps和defineEmits两个函数分别用来声明属性和自定义事件。 有如下知识点:

    • 它们在<script setup>中可以直接被使用,不需要引入,这一点还是挺容易让人出错的,因为Reactive的API都需要从vue包中引入,而defineProps和defineEmits却不用,这种不一致会让人觉得找不到规律,其实这两个这两个API都是编译器宏,它们只能在<script setup>中使用,其实规律也不是没有,可以看到两个API都是以define开头。
    • defineProps和defineEmits参数和Options API中props属性的值形式相同。
    • Vue通过defineProps和defineEmits中的参数来进行类型推断。
    • defineProps和defineEmits的参数中不能使用<script setup>中定义的变量,因为它们被提升到了setup(setup其实是个函数)的外层的模块级别,但是可以使用import导入的变量,因为import也同样被提升到了模块级别。 举例如下:
      <script setup>
        const localV = 'text';
        // 编译错误
        defineProps([localV]);
      </script>
      
      <script setup>
        import x from './x';
        defineProps([x]);
      </script>
      
  • 组件向外部暴露数据 <script setup>内部定义的变量数据默认是不向外暴露的。模版中的ref或者$parent链不会暴露任何<script setup>中的数据。 如果要想显式的暴露数据,可以使用defineExpose,可以看到该API使用define开头,说明它是一个编译期宏,示例如下:

      <script setup>
      import { ref } from 'vue';
      const a = 1;
      const b = ref(2);
      defineExpose({
        a,
        b
      });
      </script>
    

该示例中,父组件中通过模版的refs获取示例组件中暴露出的{a: 1, b: 2}。这里要注意的是ref会自动被解包。

  • 获取槽对象、获取attr 在模版中可以直接访问slotsslots和attrs。<script setup>中使用useSlots和useAttrs来访问。这两个函数都是运行时函数,需要从vue导出。示例如下:
<script setup>
import {useSlots, useAttrs} from 'vue';
const slots = useSlots();
const attrs = useAttrs();
</script>
  • <script setup>和<script>一起使用 <script setup>可以和<script>一起使用,以下几种情况,我们需要这么做:

    • 声明一些无法在<script setup>中声明的属性,例如inheritAttrs或者来自于插件的自定义的属性

    • 声明命名式的导出

    • 由于<script setup>中的代码在每次创建组件实例时都会被重复执行,当我们想执行一些只想执行一次的代码时,还是需要<script> 示例代码如下:

  • 顶层await <script setup>中可以使用顶层的await,编译完的代码实际是:async setup(),示例代码如下:

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

await并不能阻挡住组件的创建,只是setup内部await之后的代码会被延迟执行。一旦使用了await,setup便成了async setup,最近版本的vue要求这种情况下必须和Suspense结合使用,Suspense是Vue自带的组件,不需要引入,可以在Suspense中定义async setup未执行完成时的UI效果。大部分情况下,setup中会有一个远程数据获取逻辑,在数据未返回给客户端时,Suspense会显示开发人员提供的中间态UI。

<script setup >到此就讲解结束了。看到这里你对Composition API还有兴趣吗?Vue之所以受欢迎是因为它学习成本低,较容易上手,即使新手也能驾驭起来,而现在Composition API的复杂程度可能会和简单的Vue这个概念有些背离,Vue的使用正在变得越来越复杂。不过好在Vue是一个渐进式的框架,如果你觉得无法驾驭Composition API,可以继续选择使用Options API,它依然是那么的简单明了。

Composition API风格是一套成体系的新概念,肯定不只是一个<script setup>,它还提供了一系列API,下面咱们继续,来研究一下这些API。

Composition API风格的API

Composition API广义上讲是一个风格和一个概念的总称,狭义上也可以理解为一系列具体的API。由于这部分篇幅会比较长,因此我们另到一片文章讲解,待该文章出炉时,会把链接放到这里。