1 为什么要用Vue3
在学习Vue3的新特性之前,我们先来看一下Vue3设计的目的是什么,为什么要对Vue2做出很大的改变,以及Vue3到底解决了什么问题。像Vue这样全球闻名的前端框架,在任何一次改动时,设计者都是经过深思熟虑的权衡,所以,Vue3的出现肯定是解决了某些棘手的问题。下面介绍一下Vue2中遇到的问题。
1.1 Vue2对复杂功能的处理不友好
使用Vue2开发项目的过程中,随着更加复杂业务逻辑的增加,复杂组件的代码变得难以维护。尤其是一个开发人员从别的开发人员手中接过一个新项目时,这个问题更突出。究其根本原因,Vue2中的API是通过选项来组织代码,但是大部分情况下,通过逻辑来组织代码会更有意义。Vue2中缺少多个组件之间提取和复用逻辑的机制,现有的重用机制都有很多缺点。
假设我们有一个需求,要对所有的视频进行分类管理,点击不同的学科分类,可以查看不同的视频列表,如图1所示。针对这个需求,可以设置一个filter过滤功能,根据用户点击的学科分类,展示不同内容。
图 1 视频分类效果
在上面的案例中,用户点击不同的学科分类,展示的视频列表如果超出了当前页面展示的数量,例如,每个分页下展示40个视频,超出范围的要使用分页显示,效果如图2所示。
图 2 视频列表分页效果
上面的案例,如果使用Vue2.0开发,示例代码如下:
export default {
data() {
return {
filter: {}, //处理过滤功能
pagination: {} //处理分页功能
}
},
methods: {
filterMethod: () => {}, //处理过滤功能
paginationMethod: () => {} //处理分页功能
},
computed: {
...
}
}
通过上面代码可以看到,在data中已经做了filter过滤和pagination分页的相关处理,但是在下面的methods和computed中还需要继续做相关的处理,这些功能会分散到好几个部分。如果仅仅是这两个处理功能,项目的逻辑看上去还不是特别复杂,但是如果再增加搜索、收藏、排序等功能,随着功能复杂度的上升,带来的问题也愈加明显。
1.2 Vue2中mixin存在缺陷
在上一小节中提到的案例,Vue2也给出了解决方案,那就是使用mixin混入的方式。对上面案例中遇到的问题,我们可以使用mixin重写编写代码,示例代码如下:
const filterMixin = {
data() {
return {}
},
methods: {}
}
const paginationMixin = {
data() {
return {}
},
methods: {}
}
export default {
mixins: [filterMixin, paginationMixin]
}
在上面代码中,创建了两个mixin混入对象,filterMixin和paginationMixin,然后在Vue组件对象中使用mixins选项属性引入这两个对象。虽然这样可以暂时解决一些按逻辑分类的问题,但是这样做也会带来一些其他问题。
首先是会产生多个混入对象的属性和方法名称的命名冲突,其次是mixin对象所暴露的变量有什么作用,第三是把混入对象中的逻辑复用到其他的组件中,还会出现一些不可预知的问题。
1.3 Vue2对TypeScript的支持有限
Vue框架的开发者都清楚,Vue2对TypeScript的支持并不友好,这是因为在Vue中是依赖this上下文对象向外暴露属性,但是在组件中的this与普通的JavaScript中的Object对象处理的方式不同。其实,在Vue2设计时就没有考虑对TypeScript的集成和强制类型的相关问题,所以才导致在Vue2中使用TypeScript有很多阻碍。
2 Vue3.0简介
众所周知,前端技术一直更新的很快,特别是前端框架,更新速度更是极快的。在2020年4月21日晚上,Vue的作者尤雨溪在B站上直播分享了Vue3.0 Beta最新进展,直到9月19日,Vue3.0正式版才发布。这个耗时两年,历经99为代码贡献者,2600多次代码提交的大版本更新终于和众多开发者见面了。
为了减少前端开发者的学习成本,Vue2的大部分特性都完全地保留到Vue3中,你可以像使用Vue2一样,原封不动地使用Vue3,这是遵循了渐进式的准则。如果你是一个保守派,就想使用Vue2的写法,也是完全没有问题的。
Vue3增加了以下的新特性。
2.1 Vue3在性能上有很大提升
没有哪一位开发者不想要更快、更轻的框架,Vue3给开发者带来了极致的开发体验。整个Vue3的代码库被重新编写成了一系列独立的,并且实现了不同功能的模块。据官方介绍,Vue3的代码打包大小减少了41%,初次渲染速度提升55%,更新效率提升33%,内存使用率减少54%。这些数据都得益于Vue3中重构了虚拟DOM的写法,提升渲染速度。
2.2 Vue3推出了新的API
在Vue2中遇到了一些问题,例如,复杂组件的代码变得越来越难以维护,缺少一种纯粹的多组件之间提取和复用逻辑的机制。虽然Vue2中也提供了相关的解决方案,但是在Vue2中对于重用机制这一部分也存在了一些弊端。所以,Vue3中设计了Composition API,这也是我们本章重点介绍和使用的Vue3的新特性。
Composition这个单词是“组合”的意思,是Vue3中新推出的一系列API的合集,主要包括了以下API。
- ref
- reactive
- computed
- watch
- 新的生命周期函数
- 支持自定义Hooks函数
- Teleport
- Suspense
- 全局API的修改和优化
2.3 更好的TypeScript支持
如果有在Vue2中集成TypeScript的开发者应该都体会过其中的痛苦,因为Vue2在推出的时候没有把TypeScript作为一个考量范围,那么在设计Vue3的时候,设计者们就痛定思痛地考虑了这方面的问题。
Vue3的源代码全部都是使用TypeScript语法编写的,提供了非常完备的类型定义,在使用Vue3开发项目时,可以把TypeScript语法深入到各个大型项目中,让开发者更加方便的享受类型推论等一系列TypeScript的红利。同时,还可以在VSCode等编辑器中安装相关的插件,完美的使用TypeScript的各种功能。
3 Vue3.0项目搭建
3.1 Vue CLI脚手架简介
学习Vue3之前要先配置Vue3和TypeScript的开发环境,我们本章节中使用的是Vue开发团队推出的官方脚手架工具——Vue CLI。
Vue CLI是一个基于Vue.js进行快速开发的完整系统,它提供了一系列与Vue框架相关的功能,例如,启动一个本地服务器、静态校验代码格式、运行单元测试、构建生产环境等。
在安装Vue CLI脚手架工具之前,需要先检查一下Node.js的版本,推荐使用Node.js10以上的版本。在cmd命令行工具中运行命令查看Node.js的版本,命令如下:
node --version
命令运行效果如图3所示。
图3 查看Node.js版本
3.2 安装Vue CLI
配置Vue3和TypeScript的开发环境之前,要先安装Vue CLI脚手架工具,安装命令如下。
npm install -g @vue/cli
## OR
yarn global add @vue/cli
由于npm访问的是境外服务器,很多情况下会出现请求速度慢或请求不到服务器的问题,推荐使用 cnpm 的方式下载Vue CLI,cnpm安装的命令如下。
npm install -g cnpm --registry=https://registry.npm.taobao.org
使用cnpm安装Vue CLI,命令如下。
cnpm install -g @vue/cli
如果你之前安装过Vue CLI,建议执行上面的命令来更新版本。安装成功后需要查看一下当前的Vue CLI版本是否为4.x,命令如下。
vue --version
查看版本命令运行效果如图4所示。
图4 查看Vue CLI版本
本节中使用的Vue CLI的版本为4.5.11,如果你的版本与本节中的版本不一致,不会造成负面影响,只需要保证你的版本为4.5.0以上即可,因为只有4.5.0以上的版本才能创建支持最新版的Vue3的基础项目。
3.3 创建Vue3项目
Vue CLI提供了命令行和UI界面创建项目的两种方式,无论使用哪种方式,创建的流程是完全一样的,只是展示的形式不太一样而已。本节使用的是命令行的方式创建Vue3项目。
在本地硬盘中创建一个project的目录,在该目录下启动命令行工具,创建vue3-basic的项目,执行以下命令。
vue create vue3-basic
在命令行工具中输入上面的命令,按回车,会出现如图5所示的选项。
图5 选择创建Vue项目模式
在图5中有三种创建模式,前两种分别是使用Vue2和Vue3默认的模板创建,第三种是手动创建,通过键盘的上下键选择创建模式,本节使用第三种手动创建的模式。
如果在你的命令行中没有出现“Vue3 Preview”选项,说明现在使用的是旧版的Vue CLI脚手架工具,需要更新版本,更新版本的方法参考3.2章节中的安装Vue CLI命令。
由于前两项创建模式不支持TypeScript语法,所以在命令行中,使用上下键选择 “Manually select features”模式,按回车进入下一步操作。效果如图6所示。
图6 安装需要的模块
在图6中提供了一系列课插拔的支持,涵盖了Vue3项目开发中需要的各种各样的功能,充分体现了Vue CLI工具的建议式配置项目的特点。在这一步骤中同样使用上下键选择需要的功能,选中需要安装的功能后按空格键在选择和取消选择之间进行切换,选择完成后按回车键确认,然后进入下一步操作。功能选择的效果如图7所示。
图7 功能选择
在下一步操作中需要选择Vue的版本,选择Vue3的版本,按回车进入下一步。效果如图8所示。
图8 选择Vue的版本
在下一步操作的提示中,系统询问了是否需要class-style的组件来支持TypeScript。由于Vue3已经对底层代码进行了重写,不需要class也可以很方便的进行开发,而且无需额外的配置。在这一步操作中,输入英文字母“n”,然后按回车进入下一步操作。效果如图9所示。
图9 选择是否使用class-style
在下一步操作中提示是否需要Babel和TypeScript结合使用,Babel会自动添加polyfills,并转换JSX。因为我们创建的Vue3项目中没有使用到JSX,所以这一步仍然输入字母“n”,继续按回车进入下一步操作。效果如图10所示。
图10 选择是否使用Babel
下一步选择是否使用“history”路由模式,这里输入“y”,按回车继续下一步,效果如图11所示。
图11 选择路由模式
下一步是询问是将配置信息放到一个独立的文件中还是放到package.json文件中,这里选择的是放到package.js文件中,然后按回车继续进入下一步。效果如图12所示。
图12 选择配置文件的类型
最后一步是询问是否将前面步骤的选择保存为一个模板,方便在以后的项目创建中一键安装,这里选择“n”,按回车。效果如图13所示。
图13 是否保存模板
安装上面的操作步骤完成后,会进入安装流程,这个环境需要进行一段时间的等待。当命令行工具中显示如图14所示的效果,就表示项目创建成功。
图14 项目创建成功
项目创建成功后,在命令行工具中继续输入如下命令,启动本地服务器。
cd vue3-basic
npm run serve
服务器启动成功效果如图15所示。
图15 启动本地服务器
服务器启动成功后,在浏览器中访问 http://localhost:8080/
访问项目,效果如图16所示。
图16 在浏览器中访问Vue3项目
如果你在浏览器中成功打开如图16所示的页面,Vue3的项目就搭建成功了。
4 Vue3项目的目录结构
Vue3项目的目录结构如下。
- node_modules 项目的依赖管理目录
- public 公共资源管理目录
|-- favicon.ico 站点title的图标
|-- index.html 站点的静态网页
- src 源码管理目录
|-- assets 静态资源管理目录
|-- components 公共组件管理目录
|-- router 路由管理目录
|-- store 状态管理目录
|-- views 视图组件管理目录
|-- App.vue 项目根组件
|-- main.ts 项目入口文件
|-- shims-vue.d.ts 定义.vue类型的TypeScript配置文件
- package-lock.js 依赖管理配置文件
- package.js 依赖管理配置文件
- tsconfig.json TypeScript配置文件
Vue3的项目目录结构与Vue2的类似,唯一不同的是很多.js文件都改为了.ts文件。其中,src目录下的shims-vue.d.ts是用来定义vue类型的TypeScript配置文件。因为.vue结尾的Vue组件文件在TypeScript中是不能被直接识别的,所以需要使用该配置文件来说明.vue的类型,便于TypeScript进行解析。
Vue3中main.ts入口文件的源代码与Vue2也有很大的差别。我们先来看一下Vue2中的main.js的代码,示例代码如下:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
在Vue2中使用new关键字实例化Vue对象,然后通过构造函数将选项属性传入到Vue实例中。而Vue3对main.js做了修改,在Vue3中使用的是.ts类型的文件编写的入口文件,示例代码如下:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
createApp(App).use(store).use(router).mount('#app')
Vue3的main.ts文件中引入了createApp函数,然后再引入App.vue组件,调用createApp函数来创建Vue实例,将所有的模块使用Vue实例对象进行调用,而不是像Vue2中直接传入到Vue对象的构造方法中,这是Vue3做的很大的改进。
5 Composition API 详解
Composition API是Vue的下一代主要版本中最常用的语法,它是一种全新的逻辑重用和代码组织的方法。在Vue2中使用的是Options API的方式构建组件,如果要向Vue组件中添加业务逻辑,需要先填充选项属性,例如data、methods、computed等。这种方式最大的缺点是,它本身并不是有效的JavaScript代码,需要先了解模板中可以访问哪些属性,然后再使用Vue的编译器将这些属性转换成可以执行的代码,这样做既消耗了性能,有无法让使用做更好的类型检查。
Composition API设计的目的通过将当前可用组件属性作为JavaScript函数暴露出来,这种机制可以基于功能的附加API灵活地组合组件逻辑,使Composition API编写的代码更易读。
下面我们来学习Composition API的语法。
5.1 setup函数
setup()函数是vue3中专门新增的方法,可以理解为Composition Api的入口。
Composition API的主要思想是,将Vue组件的选项属性定义为 setup 函数返回的JavaScript变量,而不是将组件的功能(例如state、method、computed等)定义为对象属性。
setup函数的执行时机是在beforeCreate之后,created之前。setup函数有两个参数,第一个参数是用于接收props数据。示例代码如下:
export default {
props: {
msg: {
type: String,
default: () => {}
}
},
setup(props) {
console.log(props);
}
}
setup函数的第二个参数是一个上下文对象,这个上下文对象大致包含了一些属性,示例代码如下:
const MyComponent = {
setup(props, context) {
context.attrs
context.slots
context.parent
context.root
context.emit
context.refs
}
}
这里需要注意,在setup函数中是无法访问this的。
5.2 reactive函数
reactive是用来创建一个响应式对象,等价于2.x的Vue.observable,示例代码如下:
<template>
<div>
<p @click="incment()">
click Me!
</p>
<p>
一:{{ state.count }} 二: {{ state.addCount }}
</p>
</div>
</template>
<script>
import { reactive } from 'vue';
export default {
setup () {
const state = reactive({//创建响应式数据
count: 0,
addCount: 0
});
function incment () {
state.count++;
state.addCount = state.count * 2;
}
return {
state,
incment
};
}
};
</script>
5.3 ref函数
ref函数用来给定的值创建一个响应式的数据对象,ref函数的返回值是一个对象,这个对象上只包含一个.value属性。示例代码如下:
import { ref } from 'vue';
export default {
setup () {
const valueNumber = ref(0);
const valueString = ref('hello world!');
const valueBoolean = ref(true);
const valueNull = ref(null);
const valueUndefined = ref(undefined);
return {
valueNumber,
valueString,
valueBoolean,
valueNull,
valueUndefined
};
}
};
使用ref函数定义的响应式属性,在template中访问的方法和Vue2一样,可以直接使用模板语法的形式访问,示例代码如下:
import { ref } from 'vue';
export default {
setup () {
const value = ref(1);
return {
value,
msg: 'hello world!'
};
}
};
在template使用模板语法直接访问响应式属性,示例代码如下:
<template>
<p>
{{ value }} {{ msg }}
</p>
</template>
下面来对比一下Vue3中的ref函数与Vue2中data的区别。先使用Vue2的语法编写一个计算器的案例,示例代码如下:
//Counter.vue
export default {
data: () => ({
count: 0
}),
methods: {
increment() {
this.count++;
}
},
computed: {
double () {
return this.count * 2;
}
}
}
下面再使用Composition API定义一个完全相同功能的组件,示例代码如下:
// Counter.vue
import { ref, computed } from "vue";
export default {
setup() {
const count = ref(0);
const double = computed(() => count * 2)
function increment() {
count.value++;
}
return {
count,
double,
increment
}
}
}
在上面的示例中,使用Composition API提供的ref函数定义了一个响应式变量,其作用与Vue2的data变量几乎相同。在Vue3的示例代码中,increment方法是一个普通的JavaScript函数,需要更改子属性count的value才能更改响应式变量,这是因为使用ref函数创建的响应式变量必须是对象,以便于在传递的时候保持一致。
Composition API提供了更方便的逻辑提取方式,我们还以上面的代码为例,使用Composition提取Counter.vue组件的代码,创建useCounter.js文件,示例代码如下:
//useCounter.js
import { ref, computed } from "vue";
export default function () {
const count = ref(0);
const double = computed(() => count * 2)
function increment() {
count.value++;
}
return {
count,
double,
increment
}
}
如果要在其他组件中使用该函数,只需要将模块导入组件文件并调用它即可,导入的模块是一个函数,该函数将返回我们定义的变量,然后可以从setup函数中返回它们。示例代码如下:
// MyComponent.js
import useCounter from "./useCounter.js";
export default {
setup() {
const { count, double, increment } = useCounter();
return {
count,
double,
increment
}
}
}
这种操作还可以解决Vue2中mixins命名冲突的问题,示例代码如下:
export default {
setup () {
const { someVar1, someMethod1 } = useCompFunction1();
const { someVar2, someMethod2 } = useCompFunction2();
return {
someVar1,
someMethod1,
someVar2,
someMethod2
}
}
}
Composition API也提供了一些其他的ref辅助操作的函数。
isRef
用来判断某个值是否为ref创建出来的对象,在需要展开某个值可能是ref()创建出来的对象时使用。示例代码如下:
import { ref, isRef } from 'vue';
export default {
setup () {
const count = ref(1);
const unwrappend = isRef(count) ? count.value : count;
return {
count,
unwrappend
};
}
};
toRefs
torefs函数可以将reactive()创建出来的响应式对象转换为普通的对象,只不过这个对象上的每个属性节点都是ref()类型的响应式数据。示例代码如下:
<template>
<p>
<!-- 可以不通过state.value去获取每个属性 -->
{{ count }} {{ value }}
</p>
</template>
<script>
import { ref, reactive, toRefs } from 'vue';
export default {
setup () {
const state = reactive({
count: 0,
value: 'hello',
})
return {
...toRefs(state)
};
}
};
</script>
toRef
toRef函数为源响应式对象上的某个属性创建一个ref对象,二者内部操作的是同一个数据值,更新时二者是同步。但是与ref的区别是,使用toRef函数拷贝的是一份新的数据单独操作,更新时相互不影响,相当于深拷贝。当要将某个prop的ref传递给某个复合函数时,toRef很有用。示例代码如下:
import { reactive, ref, toRef } from 'vue'
export default {
setup () {
const m1 = reactive({
a: 1,
b: 2
})
const m2 = toRef(m1, 'a');
const m3 = ref(m1.a);
const update = () => {
// m1.a++;//m1改变时,m2也会改变
// m2.value++; //m2改变时m1同时改变
m3.value++; //m3改变的同时,m1不会改变
}
return {
m1,
m2,
m3,
update
}
}
}
5.4 computed计算属性
computed函数用来创建计算属性,返回值是一个ref的实例。创建只读的计算属性,示例代码如下:
import { ref, computed } from 'vue';
export default {
setup () {
const count = ref(0);
const double = computed(()=> count.value + 1);//1
double++;//Error: "double" is read-only
return {
count,
double
};
}
};
在使用computed函数期间,传入一个包含get和set函数的对象,可以额得到一个可读可写的计算属性。示例代码如下:
// 创建一个 ref 响应式数据
const count = ref(1)
// 创建一个 computed 计算属性
const plusOne = computed({
// 取值函数
get: () => count.value + 1,
// 赋值函数
set: val => {
count.value = val - 1
}
})
// 为计算属性赋值的操作,会触发 set 函数
plusOne.value = 9
// 触发 set 函数后,count 的值会被更新
console.log(count.value) // 输出 8
5.5 Vue3中的响应式对象
Vue2中的data和Vue3中的ref一样,都可以返回一个响应式对象,但是Vue2中使用的是object.defineProperty()来实现响应式的,这就导致Vue2的响应式出现一些限制。对于Vue2中新增一个响应式属性就会变得很困难。
在Vue2中,无法检测property的添加或者移除,对于已经创建的实例,Vue是不允许动态添加根级别的响应式属性的。如果要动态添加响应式对象的属性,可以使用Vue.set(object, propertyName, value)方法向嵌套对象中添加响应式属性。还可以使用 vm.
s
e
t
实例方法动态添加响应式属性,这也是全局
V
u
e
.
s
e
t
(
)
方法的别名。这种操作对于一个
V
u
e
的初学者来说,很多时候需要小心翼翼的去判断到底什么情况下需要用
set 实例方法动态添加响应式属性,这也是全局 Vue.set() 方法的别名。 这种操作对于一个Vue的初学者来说,很多时候需要小心翼翼的去判断到底什么情况下需要用
set实例方法动态添加响应式属性,这也是全局Vue.set()方法的别名。这种操作对于一个Vue的初学者来说,很多时候需要小心翼翼的去判断到底什么情况下需要用set,什么情况下可以直接触发响应式。这就对初学者带来了很多困扰。
在Vue3中,这些问题都将成为过去式。Vue3采用了ES6的一个新特性,使用Proxy来实现响应式。Proxy对象用于定义基本操作的一个自定义行为,简单来说,Proxy对象就是可以让你对一个JavaScript中一切合法对象的基本操作进行自定义,然后用自定义的操作去覆盖对象的一些基本操作。
我们可以通过下面的两段代码来学习Vue3中是如何使用Proxy进行优化的。
Vue2中的响应式处理,示例代码如下:
Object.defineProperty(data, 'count', {
get() {},
set() {}
})
Vue3中对于响应式的优化,示例代码如下:
new Proxy(data, {
get(key) {},
set(key, value) {}
})
通过上面两段代码可以看出,Proxy是在更高维度上进行一个属性拦截修改的。我们先看Vue2的代码示例。对于给定的data对象,date对象中有一个count属性,需要根据具体的count去修改set函数。所以,在Vue2中对于对象上的新增属性是无能为力的。
而Vue3中使用的是Proxy进行拦截的,这里无需知道具体的key是什么,拦截的是修改data上任意的key和读取data上任意的key的操作。所以,无论是已有的key还是新增的key都可以被拦截。
Proxy更加强大之处在于,除了getter和setter对属性的拦截外,还可以拦截更多的操作符。
5.6 生命周期的改变
在Vue3中的生命周期和Vue2中的生命周期的用法是一样的。所谓生命周期,就是在一个组件从创建到销毁的全过程,会暴露出一系列的钩子函数供开发者在对应阶段进行相关的操作。
除了Vue2中已有的一部分生命周期钩子,Vue3还增加了一些新的生命周期,可以直接导入 onXXX 一族的函数来注册生命周期钩子。示例代码如下:
import { onMounted, onUpdated, onUnmounted } from 'vue'
const MyComponent = {
setup() {
onMounted(() => {
console.log('mounted!')
})
onUpdated(() => {
console.log('updated!')
})
onUnmounted(() => {
console.log('unmounted!')
})
},
}
Vue3的生命周期钩子函数只能在 setup() 期间同步使用,因为它们依赖于内部的全局状态来定位当前组件实例,不在当前组件下调用这些函数会抛出一个错误。组件实例上下文也是在生命周期钩子同步执行期间设置的,因此,在卸载组件时,在生命周期钩子内部同步创建的侦听器和计算状态也将自动删除。
Vue3中与Vue2的生命周期相对应的组合式API如下。
- beforeCreate -> 使用 setup()
- created -> 使用 setup()
- beforeMount -> onBeforeMount
- mounted -> onMounted
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated
- beforeDestroy -> onBeforeUnmount
- destroyed -> onUnmounted
- errorCaptured -> onErrorCaptured
Vue2生命周期的初创期钩子beforeCreate和created,在Vue3中使用setup()替代了。除了和 2.x 生命周期等效项之外,组合式 API 还提供了以下调试钩子函数。
- onRenderTracked
- onRenderTriggered
两个钩子函数都接收一个 DebuggerEvent,与 watchEffect 参数选项中的 onTrack 和 onTrigger 类似。示例代码如下:
export default {
onRenderTriggered(e) {
debugger
// 检查哪个依赖性导致组件重新渲染
},
}
5.7 watch侦测变化
watch函数用来监视某些数据项的变化,从而触发某些特定的操作,看下面这个案例,会实时监听count值的变化。示例代码如下:
import { ref, watch } from 'vue';
export default {
setup () {
const count = ref(1);
watch(()=>{
console.log(count.value, 'value');
})
setInterval(()=>{
count.value++;
},1000);
return {
count,
};
}
};
watch还可以监听指定的数据源,例如监听reactive的数据变化。示例代码如下:
import { watch, reactive } from 'vue';
export default {
setup () {
const state = reactive({
count: 0
})
watch(()=>state.count,(count, prevCount)=>{
console.log(count, prevCount);//变化后的值 变化前的值
})
setInterval(()=>{
state.count++;
},1000);
return {
state
};
}
};
watch用于监听ref类型的数据变化,示例代码如下:
import { ref, watch } from 'vue';
export default {
setup () {
const count = ref(0);
watch(count,(count, prevCount)=>{
console.log(count, prevCount);//变化后的值 变化前的值
})
setInterval(()=>{
count.value++;
},1000);
return {
count
};
}
};
在setup()函数内创建的watch监视,会在当前组件被销毁的时候自动停止。如果想要明确的停止某个监视,可以调用watch()函数的返回值即可。示例代码如下:
// 创建监视,并得到 停止函数
const stop = watch(() => {
/* ... */
})
// 调用停止函数,清除对应的监视
stop()
5.8 Vue3更好的支持TypeScript
Vue2依赖于this上下文对象向外暴露属性,但是在设计Vue2的API时,并没有考虑到与TypeScript集成。如果在Vue2中想要使用TypeScript语法的话,需要使用vue class或者是vue extends的方式来集成对TypeScript的支持。到了Vue3,Vue官方团队推出了一个新的方式定义component,这个方式被称为 definedComponent。示例代码如下:
import { defineComponent } from 'vue';
export default defineComponent({
setup(){
function demo(str: String){
console.log(str)
}
return {
demo
}
}
});
5.9 Teleport传送门
Teleport 是一种能够将我们的模板移动到 DOM 中 Vue app 之外的其他位置的技术。例如,在项目中,像modals、toast等这样的元素,很多情况下,需要将它完全的和Vue应用的DOM完全剥离,这样会更加便于项目的管理。如果类似于modals、toast这样的注解嵌套在Vue的某个组件内部时,那么处理嵌套组件的定位、z-index和样式就会变得很困难。
Teleport就很好的解决了这一类问题。下面用一个例子来说明Teleport的用法。
在index.html中添加一个div元素,并指定其id属性值。示例代码如下:
<div id="app"></div>
<div id="teleport-target"></div>
在HelloWorld.vue文件中,添加teleport的组件代码,teleport组件上的to属性要和index.html新增的div的id选择器保持一致。示例代码如下:
<button @click="showToast" class="btn">打开 toast</button>
<!-- to 属性就是目标位置 -->
<teleport to="#teleport-target">
<div v-if="visible" class="toast-wrap">
<div class="toast-msg">我是一个 Toast 文案</div>
</div>
</teleport>
在HelloWorld.vue文件中添加script脚本,示例代码如下:
import { ref } from 'vue';
export default {
setup() {
// toast 的封装
const visible = ref(false);
let timer;
const showToast = () => {
visible.value = true;
clearTimeout(timer);
timer = setTimeout(() => {
visible.value = false;
}, 2000);
}
return {
visible,
showToast
}
}
}
在上面的示例中,使用teleport组件,通过to属性指定该组件渲染的位置与
5.10 Suspense异步请求
Vue3新增了Suspense组件,可以允许应用程序在等待异步组件时渲染一些后备内容,帮助开发者创建一个平滑的用户体验。Suspense组件非常容易理解,也不需要任何额外的导入。
例如,有一个异步的ArticleInfo.vue的组件,其中setup方法是异步的,用于返回加载用户的数据。示例代码如下:
async function getArticleInfo() {
// 一些异步API调用
return { article }
}export default {
async setup () {
var { article } = await getArticleInfo()
return {
article
}
}}
再创建一个ArticlePost.vue组件,在该组件中包含ArticleInfo.vue组件。如果要在等待组件获取数据并解析时显示“正在加载…”的内容,只需要三步就可以实现Suspense。
- 将异步组件包装在<template #default>标记中
- 在我们的Async组件的旁边添加一个兄弟姐妹,标签为<template #fallback>
- 将两个组件都包装在组件中
使用插槽,Suspense将渲染后备内容,直到默认内容准备就绪。然后,它将自动切换以显示我们的异步组件。示例代码如下:
<Suspense>
<template #default>
<article-info/>
</template>
<template #fallback>
<div>正在拼了命的加载…</div>
</template>
</Suspense>
5.11 全局API修改
Vue 2.x 有许多全局 API 和配置,这些 API 和配置可以全局改变 Vue 的行为。例如,要创建全局组件,可以使用 Vue.component
这样的 API。示例代码如下:
Vue.component('button-counter', {
data: () => ({
count: 0
}),
template: '<button @click="count++">Clicked {{ count }} times.</button>'
})
虽然这种声明方式很方便,但它也会导致一些问题。在Vue2中只能通过new Vue()创建根Vue实例,从同一个Vue构造函数创建的每个根实例共享相同的全局配置,因此,在测试期间,全局配置很容易意外地污染其他测试用例。
Vue3中增加了createApp这个新的全局API,调用createApp返回一个应用实例。示例代码如下:
import { createApp } from 'vue'
const app = createApp({})
任何全局改变Vue行为的API在使用了createApp之后,都会转移到应用实例上。如下表所示,Vue2中的全局API转移到了Vue3中的实例API上面。
Vue2全局API | Vue3实例API(app) |
---|---|
Vue.config | app.config |
Vue.config.productionTip | removed |
Vue.config.ignoredElements | app.config.isCustomElement |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |
所有其他不全局改变行为的全局 API 现在被命名为 exports。