学习并使用Vue3
学习 Vue3
什么是 Vue3 ?
2020年9月发布正式版的 vue3,vue3 很好兼容了大部分 vue2 的特性,因此熟悉 vue2 的小伙伴能快速上手 vue3。
vue3 较 vue2 来说,有几个非常出色的改进:
- 体积
- 速度
- 易维护性
- 更好兼容 ts
接下来我们就聊聊关于 vue3 的部分知识点,其余知识点具体可查阅 vue3中文文档。
setup
vue3 新增的 option,只在组件初始化时执行一次, setup 功能很强大,vue3 中所有的组合 API 均在此使用。
以往 vue2 的功能基本都能在 setup 中实现,例如 state 定义响应式数据,methods 声明方法,还有之后说的生命周期组合 API 等等都可在 setup 中实现,所以 setup 非常重要!
-
setup 没有 this,故无法通过 this 访问 data / computed / methods / props
-
setup 返回值
- 一般返回在 setup 中定义的响应式数据以及声明操作数据的方法。
setup() { // 使用 reactive 定义一个引用类型的响应式数据 const user = reactive({ name: '小红', age: 18 }); // 声明操作数据的方法 const changeName = () => { user.name = '小明' } // 返回响应式数据和方法 return { user, changeName } }
- vue3 中尽量不要使用setup 与 state 和 methods 一起混着使用
- setup 返回的响应式数据会与 state 中定义的响应式数据合并成组件对象的属性
- setup 返回的方法会与 methods 定义的方法合并成组件对象的方法
- 如果 setup 返回的属性 / 方法 与 state / methods 中的重名,则 setup 中的属性 / 方法会覆盖 state / methods 中的重名属性 / 方法。
- 最重要一点:setup 中没有 this,故无法操作 state 中的属性以及 methods 中的方法,但 methods 却可以访问 setup 中定义的属性和方法!
-
setup 参数
- setup(props, context) / setup(props, {attrs, slots, emit})
- props: 包含 props 配置声明且传入了的所有属性的对象
- attrs: 包含没有在 props 配置中声明的属性的对象, 相当于 this.$attrs
- slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots
- emit: 用来分发自定义事件的函数, 相当于 this.$emit
定义响应式数据
在 vue2 中定义响应式数据,我们都是在 state 中声明定义的,但在 vue3 中我们基本是不会再去使用 state 了。因为我们有了更好的定义方法:ref 和 reactive。
ref
-
主要用来定义一个基本类型的响应式数据,例如 Number,String,Boolean。
-
语法:const xxx = ref(基本类型数据);
const name = ref('小明');
-
ref 也可以用来定义引用类型的响应式数据,内部会自动将引用类型数据转换为 reactive 的代理对象再存储在引用对象的 value 中,既然有 reactive,我们没必要使用 ref 来定义引用类型响应式数据。
-
ref 定义的响应式数据是一个引用对象(reference),数据值存储在引用对象的 value 中,故使用 ref 定义的响应式数据时有两点需要注意:
- js 中操作 ref 定义的响应式数据时需要用到
xxx.vaule
- template 中会自动处理 ref 定义的引用对象,所以在 tempalte 中可忽略
.value
,直接使用xxx
来显示响应式数据值。
<template> <div> <!-- tempalte 中可忽略 .value 而直接使用变量名 --> 我是{{name}} </div> </template> <script lang="ts"> import { defineComponent, ref } from 'vue'; export default defineComponent({ name: 'App', setup() { // 使用 ref 定义一个基本类型的响应式数据 const name = ref('小明') // js 中操作 ref 定义的响应式数据需要用到 xxx.value name.value = '小红' return { name } } }) </script>
- js 中操作 ref 定义的响应式数据时需要用到
-
ref 也可以用来获取元素
<template> <!-- ref 设置为 myInputRef --> <input type="text" ref="myInputRef" /> </template> <script lang="ts"> import { defineComponent, onMounted, ref } from 'vue' export default defineComponent({ name: 'App', setup() { // 直接通过 ref 获取 DOM 元素 const myInputRef = ref<HTMLElement | null>(null) onMounted(() => { myInputRef.value && myInputRef.value.focus() }) return { myInputRef } }, }) </script>
reactive
-
主要用来定义一个引用类型的响应式数据,例如 Object。
-
语法:const xxx = reactive(引用类型数据);
// 使用 reactive 定义一个引用类型的响应式数据 const user = reactive({ name: '小红', age: 18 });
-
深层次的响应式,嵌套的数据也是响应式
-
基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据
计算属性与监视
-
computed 函数:
- 与 vue2 中的 computed 配置功能一致
- 只有 getter
- 有 getter 和 setter
// 只有 getter 的计算属性 const fullName = computed(() => { return user.firstName + '-' + user.lastName }) // 有 getter 与 setter 的计算属性 const fullName1 = computed({ get() { return user.firstName + '-' + user.lastName }, set(value: string) { const names = value.split('-') user.firstName = names[0] user.lastName = names[1] } })
-
watch 函数:
- 与 vue2 中的 watch 配置功能一致
- 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
- 默认初始时不执行回调, 但可以通过配置 immediate 为 true, 来指定初始时立即执行第一次
- 通过配置 deep 为 true, 来开启数据深度监视
watch( user, // 监视的数据 () => { // 监视的数据发生变化时触发的回调 name.value = user.firstName + '-' + user.lastName }, { immediate: true, // 设置是否初始化立即执行一次, 默认是 false deep: true // 设置是否开启深度监视, 默认是 false } )
/* watch一个数据 默认在数据发生改变时执行回调 */ watch(fullName3, value => { console.log('watch') })
/* watch多个数据: 使用数组来指定 如果是 ref 对象, 直接指定 如果是 reactive 对象中的属性, 必须通过函数来指定 */ watch([() => user.firstName, fullName], values => { console.log('监视多个数据', values) })
-
watchEffect 函数
- 不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
- 默认初始时就会执行第一次, 从而可以收集需要监视的数据
- 监视数据发生变化时回调
watchEffect(() => { // 回调中使用了 user 中的 firstName, lastName 属性,故组件会直接监视这两个数据的变化 fullName.value = user.firstName + '-' + user.lastName })
生命周期
让我们再来比较 vue3 与 vue2 的生命周期,看看有什么变化吧。
vue2 的生命周期
vue3 的生命周期
vue3较vue2生命周期的变化
vue3 中将生命周期函数改为对应的组合式 API。
- beforeCreate -> setup()
- create -> setup()
- beforeMount -> onBeforeMount
- mounted -> onMounted
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated
- beforeDestroy -> onBeforeUnmount
- destroyed -> onUnmounted
- errorCaptured -> onErrorCaptured
vue3 也支持大部分 vue2 生命周期函数写法,需要注意的是:
-
beforeDestroy
和destroyed
两个生命周期需要改为beforeUnmount
和unmounted
-
vue3 中的组合式 API 较 vue2 传统生命周期函数先执行。
// vue3 中需要引入组合式 API 才可使用 import { defineComponent, onBeforeMount, onMounted } from 'vue'; export default defineComponent({ name: 'App', beforeCreate(){ console.log('vue2: beforeCreate') }, // vue3 中也支持大部分 vue2 生命周期函数的写法,只是执行慢于组合式 API create(){ console.log('vue2: create') }, beforeMount(){ console.log('vue2: beforeMount') }, mounted(){ console.log('vue2: mounted') }, // vue3 生命周期组合式 API 需要在 setup 中使用 setup(){ console.log('vue3: beforeCreate / create ') onBeforeMount(() => { console.log('vue3: onBeforeMount') }) onMounted(() => { console.log('vue3: onMounted') }) } })
执行结果:
hooks
作用:封装可复用的功能函数,自定义 hook 可使复用功能代码更简洁清晰,易读性更强。
toRef 和 toRefs
toRef
-
toRef 用于为源响应式对象上的属性新建一个 ref,从而保持对其源对象属性的响应式连接。
-
toRef 后的 ref 数据不是原始数据的拷贝,而是引用,改变结果数据的值也会同时改变原始数据
-
区别 ref : 拷贝了一份新的数据值单独操作, 更新时相互不影响
-
应用: 当要将某个 prop 的 ref 传递给复合函数时,toRef 很有用
-
接收两个参数:源响应式对象和属性名,返回一个 ref 数据
-
语法:const xxx = toRef(obj, 属性名)
const car = { price: '100w', count: 20 } const price = toRef(car, "price") const count = toRef(car, "count") // 解构出来的属性均为 ref 属性,在 js 中操作需要添加 .value console.log(price.value)
toRefs
-
toRefs 用于将响应式对象转换为普通对象,该普通对象的每个属性都是指向原始对象相应属性的 ref。
-
利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性。
-
语法:const {属性名…} = toRefs(obj)
const user = reactive({ name: '小红', age: 18 }) // 结合 es6 的解构赋值,利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性。 const {name, age} = toRefs(user) // 解构出来的属性均为 ref 属性,在 js 中操作需要添加 .value console.log(name.value)
新增组件
Fragment(片断)
-
vue2 中:组件必须有一个根标签
-
vue3 中:组件可以没有根标签,内部会将多个标签包含在一个 Fragment 虚拟元素中
-
作用: 减少标签层级,减少内存占用
<template> <!-- vue2 中需要根标签 --> <div> <h2>vue2</h2> <h2>vue2</h2> </div> </template> <template> <!-- vue3 中不需要根标签 --> <h2>vue3</h2> <h2>vue3</h2> </template>
Teleport(瞬移)
-
Teleport 提供了一种干净的方法, 让组件的 html 在父组件界面外的特定标签(很可能是 body)下插入显示。
<template> <div class="container"> 外层 container <!-- 使用 Teleport,并设置 to 为 body 即可实现将包裹住的代码瞬移到 body 下 --> <Teleport to="body"> <div class="inner"> 内层 inner </div> </Teleport> </div> </template>
由下图不难看出 Teleport 包住的代码已经成为 body 的直接子标签了。
Suspense(不确定的)
-
主要用于应用程序在等待异步组件时渲染一些后备内容,例如 loading…从而提高用户体验。
<template> <Suspense> <!-- 异步组件加载完成显示异步组件内容 --> <template #default> <son></son> </template> <!-- 异步组件还未加载完时显示执行下方内容 --> <template v-slot:fallback> <h1>Loading...</h1> </template> </Suspense> </template>