一、 背景介绍
1、composition api
可以思考一下🤔?是以组件划分还是功能函数划分---
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 函数包含了两个入参:
| 参数 | 类型 | 含义 | 是否必传 | 其他 | |||
|---|---|---|---|---|---|---|---|
| props | object | 由父组件传递下来的数据 | 否 | 它是响应式的(只要你不解构它,或者使用 toRef / toRefs 进行响应式数据转换),当传入新的 prop 时,它将被更新。 | |||
| context | object | 组件的执行上下文 | 否 | 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 的返回值
- 返回一个对象
- 返回一个渲染函数,该函数可以直接使用在同一作用域中声明的响应式状态(这个函数的方法有一点像React.creatElement,可以自定义渲染的内容。)- 暂时没使用过。
(3)setup 的生命周期
在 3.x ,setup 的执行时机比 2.x 的 beforeCreate 和 created 还早,可以完全代替原来的这 2 个钩子函数。
二、Vue3.X 常用新特性及使用方法
1、setup
如下有几张图,大家初步判断一下写法的对错 or 是否会报错( ✅ ❌ )
如图1:
- console 是否能够正确输出?
- 图片head是否能够正确获取?
- setup 用法是否正确?
答案:1、可以 2、可以 3、正确
如图2:
- console 是否能够正确输出?
- 图片head是否能够正确获取?
- setup 和 defineComponent 用法是否正确?
答案:1、不可以 2、不可以 3、不正确
如图3:
- console 是否能够正确输出?
- 图片head是否能够正确获取?
- setup 和 defineComponent 用法是否正确?
答案: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 文件会报错,但是页面仍然可以正常展示。
如上 App.vue 效果预览图:(为什么呢?该如何正确导出该组件?)
vue 3.0的三种导出方式
- 直接导出
export default{
name: 'myComponent'
}
- 使用defineComponent导出
import {defineComponent} from 'vue'
export default defineComponent({
name: 'myComponent'
})
- 使用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的写法】
如下图是,上图代码的预览图效果,可以看出vue2.0 和 vue3.0 语法可以共同存在,现实与打印都正常。那我们应该如何使用?
vue3.0 到底可以有多少种组合搭配写法,官方是否推荐?
**
使用了 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等
2、ref、reactive、toRef、toRefs
2.1 ref / reactive
如上,我们通过变量声明,ref和reactive的方式定义了3个变量,控制台输出打印
那这两种方式有什么不一样的呢?
发现用reactive定义number变量时,vue给出了一个警告,提示这个值不能被reactive创建,这是为什么呢?
接下来我们查看一下Reative源码:
从源码中可以看到,当使用reactive定义数据时,会先进行判断定义的数据是否是对象,是对象的话才会继续进行数据响应式的处理,反之就直接被return出来了。
由此我们可以看出来官方在使用reactive这个API的时候更推荐用来定义对象类型的数据。
因此上面的代码,我们可以改成:
总结:
- reactive和ref都是用来定义响应式数据的,而reactive更推荐用来定义对象,ref更推荐定义基础数据类型,但是ref也可以定义数组和对象
- 在访问数据的时候,ref需要使用.value,而reactive不需要
2.2 toRef / toRefs
各自的作用:两个 API 的拼写非常接近,顾名思义,一个是只转换一个字段,一个是转换所有字段。
我们先定义好一个 reactive 变量:
(1)如何使用 toRef
toRef 接收 2 个参数,第一个是 reactive 对象, 第二个是要转换的 key 。
在这里我们只想转换 userInfo 里的 name ,只需要这样操作: (之后读取和赋值就使用 name.value,它会同时更新 name 和 userInfo.name。)
注意事项:
在 toRef 的过程中,如果使用了原对象上面不存在的 key ,那么定义出来的变量的 value 将会是 undefined 。
如果你对这个不存在的 key 的 ref 变量,进行了 value 赋值,那么原来的对象也会同步增加这个 key,其值也会同步更新。
(2)如何使用 toRefs
toRefs 接收 1 个参数,是一个 reactive 对象。 (这个新的 userInfoRefs ,本身是个普通对象,但是它的每个字段,都是与原来关联的 ref 变量。)
(3)为什么要进行转换
关于为什么要出这么 2 个 API ,官方文档没有特别说明,不过经过自己的一些实际使用,可能知道一些使用理由。 ref 和 reactive 这两者的好处就不重复了,但是在使用的过程中,各自都有各自不方便的地方:
- ref 虽然在 template 里使用起来方便,但比较烦的一点是在
script 里进行读取/赋值的时候,要一直记得加上.value,否则bug就来了 - 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 会需要一些父组件页面的参数
第二步:页面有多个弹窗,定义多个常量和函数,该如何优化?
当时思考的优化方案是,把 【仅弹窗】 相关的逻辑抽离成 hooks 类似的函数。意思是,对于页面按钮而言,他只需要关心 【点击】 这个动作,仅此而已!而弹窗的打开、关闭、回调事件等都属于弹窗的范围,这也 【可能是】 vue3理念之一,将相同类似的代码做函数似抽离聚合。
(1)先将抽离 useModal 的 Hooks。(目前只是符合业务的建议版本,后续可以考虑将其完善)
(2)useModal 导出的实际上是函数,在 任何页面 中都可以直接调用。具体使用如下:
➡️ 父页面
好处:
- hooks 可以在任何页面获取封装的函数、常量
- 可以减少定义控制弹窗的函数,如下的 changeModelStatusModal
➡️ 子页面
此时导出来的 isShow 常量可以用来控制弹窗的显隐。
第三步:如果控制弹窗的 ID 重名该如何优化?
由于一个页面 or 不同父页面可能会引入多个弹窗,这样可能会导致弹窗 【乱弹】 的状况。此时,想到 TS 的 ENUM 去定义每个弹窗的 ID (💡如果大家还有其他想法,可以随时交流)。
由原来的只定义一个 isShow常量,改变成定义 model 对象,里面有 id 、 isShow 两个属性。
第四步:拓展思维
- 列表页面:增删改查
中后台列表页面居多,此时每个页面我们都需要去 getList 请求一下页面。useList 暴露出来 loading、result、get List等。理想状态下实际写页面只需要传入请求的 url 和 固定参数等
可能存在问题: (1)后端的返回值可能数据结构不一致,这块待斟酌 (2)目前也是简易版,待后续完善吧!
2.列表:操作项
操作项可以获取当前 row 的值,以及保存相关值等
3.工具函数:防抖截流等
是否未来的防抖截流函数,考虑直接用 hooks 的方式重构一下。更多的好处是在于使用 TS 类型
四、🔗 其他链接
- 尤雨溪浅谈为什么要使用setup
🔗链接: zhuanlan.zhihu.com/p/68477600
- Vue3 官网(组合式API)
🔗链接:v3.cn.vuejs.org/api/composi…
- Vue3 入门指南与实战案例(🌟🌟🌟🌟🌟)