Vue 3.0 理论和实践分享

184 阅读10分钟

一、 背景介绍

1、composition api

image.png

可以思考一下🤔?是以组件划分还是功能函数划分---

image.png

image.png

2、全新的 setup 函数

(1)了解setup

官方解释:Vue 3 的 Composition API 系列里,推出了一个全新的 setup 函数,新的 setup 选项在组件被创建之前执行,一旦 props 被解析完成,它就将被作为组合式 API 的入口。

🔑 TIP

说的通俗一点,就是使用 Vue 3 的生命周期的情况下,整个组件相关的业务代码,都可以丢到 setup 里编写。因为在 setup 之后,其他的生命周期才会被启用。

🚀WARNING

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

(2)setup 的参数使用

setup 函数包含了两个入参:

参数类型含义是否必传其他
propsobject由父组件传递下来的数据它是响应式的(只要你不解构它,或者使用 toRef / toRefs 进行响应式数据转换),当传入新的 prop 时,它将被更新。
contextobject组件的执行上下文attrs-非响应式对象-props 未定义的属性都将变成 attrs
slots-非响应式对象-插槽
emit-方法-触发事件
expose-一个函数,用来显式地对外暴露组件数据。【官网暂时没有,此API设计仍然在讨论中]
import { defineComponent } from 'vue'

export default defineComponent({
  setup(props, context) {
    // 业务代码写这里...

    return {
      // 需要给 template 用的数据、函数放这里 return 出去...
    }
  },
})


// context 可以结构
// setup(props, { emit, attrs, slots })

(3)setup 的返回值

  1. 返回一个对象

image.png

  1. 返回一个渲染函数,该函数可以直接使用在同一作用域中声明的响应式状态(这个函数的方法有一点像React.creatElement,可以自定义渲染的内容。)- 暂时没使用过。

image.png

(3)setup 的生命周期

在 3.x ,setup 的执行时机比 2.x 的 beforeCreate 和 created 还早,可以完全代替原来的这 2 个钩子函数。

image.png

二、Vue3.X 常用新特性及使用方法

1、setup

如下有几张图,大家初步判断一下写法的对错 or 是否会报错( ✅ ❌ )

如图1:

  1. console 是否能够正确输出?
  2. 图片head是否能够正确获取?
  3. setup 用法是否正确?

image.png

答案:1、可以 2、可以 3、正确

如图2:

  1. console 是否能够正确输出?
  2. 图片head是否能够正确获取?
  3. setup 和 defineComponent 用法是否正确?

image.png

答案:1、不可以 2、不可以 3、不正确

如图3:

  1. console 是否能够正确输出?
  2. 图片head是否能够正确获取?
  3. setup 和 defineComponent 用法是否正确?

image.png

答案:1、不可以 2、不可以 3、不正确

1.1 <script setup> 的用法及优势

(1)<script setup> 的用法

1、当使用 <script setup> 的时候,任何在 <script setup> 声明的顶层的绑定 (包括变量,函数声明,以及 import 引入的内容) 都能在模板中直接使用。

2、<script setup> 范围里的值也能被直接作为自定义组件的标签名使用。不需要在定义 components.

<script setup>
import MyComponent from './MyComponent.vue'
</script>

<template>
  <MyComponent />
</template>

3、由于组件被引用为变量而不是作为字符串键来注册的,在 <script setup> 中要使用动态组件的时候,就应该使用动态的 :is 来绑定(请注意组件是如何在三元表达式中被当做变量使用的。)

<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>

<template>
  <component :is="Foo" />
  <component :is="someCondition ? Foo : Bar" />
</template>

(2)<script setup> 的优势

<script setup> 是在单文件组件 (SFC) 中使用 组合式 API 的编译时语法糖。相比于普通的 <script> 语法,它具有更多优势:

  • 更少的样板内容,更简洁的代码。
  • 能够使用纯 TypeScript 声明 props 和抛出事件。
  • 更好的运行时性能 (其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)。
  • 更好的 IDE 类型推断性能 (减少语言服务器从代码中抽离类型的工作)。

1.2 vue3.x 如何导出 setup 的 vue 组件

如下图中的 App.vue 文件,引入的 Setup1.vue 文件会报错,但是页面仍然可以正常展示。

image.png

如上 App.vue 效果预览图:(为什么呢?该如何正确导出该组件?)

image.png

vue 3.0的三种导出方式

  1. 直接导出
export default{
	name: 'myComponent'
}
  1. 使用defineComponent导出
import {defineComponent} from 'vue'
export default defineComponent({
    name: 'myComponent'
})
  1. 使用vue-class-component导出(不推荐,因为不支持3.0的组合 api写法)- 我没有实际尝试过,感兴趣的可以自己探究一下。
import Vue from 'vue'
import Options from 'vue-class-component'

// Define the component in class-style
// 最新版Component注解已经改为Options
@Options({
  computed: mapGetters([
    'posts'
  ]),

1.3 defineComponent 中的 setup

如下有几张图,大家初步判断一下写法的是否支持 or 如果支持考虑优劣势( ✅ ❌ )

【DcSetup.vue 文件中的红框部分是 vue2.0的写法】

image.png

如下图是,上图代码的预览图效果,可以看出vue2.0 和 vue3.0 语法可以共同存在,现实与打印都正常。那我们应该如何使用?

image.png

vue3.0 到底可以有多少种组合搭配写法,官方是否推荐?

image.png

** 使用了 Vue3,是否都要遵循用 Composition API 的形式去写页面?**

答案是否定的。需要注意一点:Vue3 并没有废弃 Options API,甚至还会全力支持兼容 Vue2 语法的工作。而 CompositionAPI 出现的背景主要是为了解决逻辑抽象和和复用的问题,但不意味着它成为了 Vue3 的标准。

如何区分场景使用Options API or Composition API主要看业务逻辑的复杂程序,例如一些简单的 toast/button等基础组件,用options API形式会更加清晰和简洁。而相对复杂的业务逻辑,可以用Composition API,可以把单独一块逻辑抽离到一个模块。

下图是vue3.0官方书中,对于 2.0 和 3.0 写法的推荐。里面写到不建议将 setup 与 Vue2 中其他组件选项混合使用,例如:data、watch、methods等

image.png

2、ref、reactive、toRef、toRefs

2.1 ref / reactive

image.png

如上,我们通过变量声明,ref和reactive的方式定义了3个变量,控制台输出打印

image.png

image.png

那这两种方式有什么不一样的呢? 发现用reactive定义number变量时,vue给出了一个警告,提示这个值不能被reactive创建,这是为什么呢?

image.png

接下来我们查看一下Reative源码:

image.png

从源码中可以看到,当使用reactive定义数据时,会先进行判断定义的数据是否是对象,是对象的话才会继续进行数据响应式的处理,反之就直接被return出来了。

由此我们可以看出来官方在使用reactive这个API的时候更推荐用来定义对象类型的数据。

因此上面的代码,我们可以改成:

image.png

总结:

  1. reactive和ref都是用来定义响应式数据的,而reactive更推荐用来定义对象,ref更推荐定义基础数据类型,但是ref也可以定义数组和对象
  2. 在访问数据的时候,ref需要使用.value,而reactive不需要

2.2 toRef / toRefs

各自的作用:两个 API 的拼写非常接近,顾名思义,一个是只转换一个字段,一个是转换所有字段。

image.png

我们先定义好一个 reactive 变量:

image.png

image.png

(1)如何使用 toRef

toRef 接收 2 个参数,第一个是 reactive 对象, 第二个是要转换的 key 。

在这里我们只想转换 userInfo 里的 name ,只需要这样操作: (之后读取和赋值就使用 name.value,它会同时更新 name 和 userInfo.name。)

image.png

注意事项:

在 toRef 的过程中,如果使用了原对象上面不存在的 key ,那么定义出来的变量的 value 将会是 undefined 。

如果你对这个不存在的 key 的 ref 变量,进行了 value 赋值,那么原来的对象也会同步增加这个 key,其值也会同步更新。

(2)如何使用 toRefs

toRefs 接收 1 个参数,是一个 reactive 对象。 (这个新的 userInfoRefs ,本身是个普通对象,但是它的每个字段,都是与原来关联的 ref 变量。)

image.png

(3)为什么要进行转换

关于为什么要出这么 2 个 API ,官方文档没有特别说明,不过经过自己的一些实际使用,可能知道一些使用理由。 ref 和 reactive 这两者的好处就不重复了,但是在使用的过程中,各自都有各自不方便的地方:

  1. ref 虽然在 template 里使用起来方便,但比较烦的一点是在 script 里进行读取/赋值的时候,要一直记得加上 .value ,否则bug就来了
  2. reactive 虽然在使用的时候,因为你知道它本身是一个 Object 类型,所以你不会忘记 foo.bar 这样的格式去操作,但是在 template 渲染的时候,你又因此不得不每次都使用 foo.bar 的格式去渲染

那么有没有办法,既可以在编写 script 的时候不容易出错,在写 template 的时候又比较简单呢?于是, toRef 和 toRefs 因此诞生。

三、 Vue3.X 实践落地

本文先以日常最常见的 “弹窗” 做分享

第一步:如何写一个弹窗

Parent.vue 中按照 vue 2.0 的写法引入自定义弹窗(这几个弹窗的页面内容完全不一样,并且有复杂的业务逻辑),如下:VersionModal、ModelStatusModal等

可能导致的问题有

1、每个 modal 都定义了一个 showModal 的常量,去控制弹窗的显隐。

2、每个 modal 都需要从【父组件】传入进去事件函数,此时可能会定义多个事件

3、每个 modal 会需要一些父组件页面的参数

image.png

image.png

第二步:页面有多个弹窗,定义多个常量和函数,该如何优化?

当时思考的优化方案是,把 【仅弹窗】 相关的逻辑抽离成 hooks 类似的函数。意思是,对于页面按钮而言,他只需要关心 【点击】 这个动作,仅此而已!而弹窗的打开、关闭、回调事件等都属于弹窗的范围,这也 【可能是】 vue3理念之一,将相同类似的代码做函数似抽离聚合。

(1)先将抽离 useModal 的 Hooks。(目前只是符合业务的建议版本,后续可以考虑将其完善)

image.png

(2)useModal 导出的实际上是函数,在 任何页面 中都可以直接调用。具体使用如下:

➡️ 父页面

image.png

好处:

  1. hooks 可以在任何页面获取封装的函数、常量
  2. 可以减少定义控制弹窗的函数,如下的 changeModelStatusModal

➡️ 子页面

此时导出来的 isShow 常量可以用来控制弹窗的显隐。

image.png

第三步:如果控制弹窗的 ID 重名该如何优化?

由于一个页面 or 不同父页面可能会引入多个弹窗,这样可能会导致弹窗 【乱弹】 的状况。此时,想到 TS 的 ENUM 去定义每个弹窗的 ID (💡如果大家还有其他想法,可以随时交流)。

由原来的只定义一个 isShow常量,改变成定义 model 对象,里面有 id 、 isShow 两个属性。

image.png

第四步:拓展思维

  1. 列表页面:增删改查

中后台列表页面居多,此时每个页面我们都需要去 getList 请求一下页面。useList 暴露出来 loading、result、get List等。理想状态下实际写页面只需要传入请求的 url 和 固定参数等

可能存在问题: (1)后端的返回值可能数据结构不一致,这块待斟酌 (2)目前也是简易版,待后续完善吧!

image.png

2.列表:操作项

操作项可以获取当前 row 的值,以及保存相关值等

3.工具函数:防抖截流等

是否未来的防抖截流函数,考虑直接用 hooks 的方式重构一下。更多的好处是在于使用 TS 类型

image.png

四、🔗 其他链接

  1. 尤雨溪浅谈为什么要使用setup

🔗链接: zhuanlan.zhihu.com/p/68477600

  1. Vue3 官网(组合式API)

🔗链接:v3.cn.vuejs.org/api/composi…

  1. Vue3 入门指南与实战案例(🌟🌟🌟🌟🌟)

🔗链接:vue3.chengpeiquan.com/