前言:Vue3.0在2022 年 2 月 7 日成为新的默认版本,那么我们也不能停下学习的脚步。本文内容记录了个人在Vue3知识点以及学习时的个人的看法,如有错误的地方,欢迎指出。
常用的Composition API
Composition API
概念:
Composition API(组合式a pi),他是一系列api的集合,使我们可以使用函数而不是声明选项的方式书写 Vue 组件。
Options API 存在的问题
使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。
Composition API 的优势
我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。
1. Setup
-
理解:setup是 Vue3中的一个新的配置项,是一个函数。
-
setup是所有Compositon API(组合式API)表演的舞台,所有组合式api均要在setup里使用。
-
组件里的数据、方法等,都要配置在setup里。
-
setup的返回值的两种形式:
- 若返回值是一个对象,则对象里的方法、属性可以直接在模板中使用。(重要)
- 若返回值是一个渲染函数,则可自定义渲染内容。(了解)
-
注意点:
-
尽量不要与vue2的配置项混用。
- vue2的配置(data,methods,computed...)可以访问到setup中的属性和方法。
- 但是setup不能访问vue2的配置中的数据。
- 如果数据有重名,已setup的优先。
-
setup在 beforeCreate 之前执行,this是undefined
-
setup接收的参数:
-
props:值为对象,包含组件外部传过来,且组件内部声明接收了的属性。
-
context(上下文对象):
- attrs:值为对象,包含组件外部传过来,但是没有在
props中声明接收的属性,相当于this.$attrs. - slots:收到的插槽内容,想当于
this.$slots。现在的vue中具名插槽和作用域插槽在父组件调用时需要使用v-slot,以前具名插槽的slot和作用域插槽的slot-scope及scope写法已弃用。 - emit:触发自定义事件,相当于
this.$emit.
- attrs:值为对象,包含组件外部传过来,但是没有在
-
-
2. ref函数
-
作用:定义一个基本类型的响应式数据,创建一个包含响应式数据的引用对象(reference对象,简称ref对象)
-
用法:
// 需要通过 `import {ref} from 'vue'`进行引入才能使用。 let name=ref('张三')- 在JS中操作数据:
name.value - 在模板中操作数据:不需要.value,直接使用名字就行,例如:
<div>{{name}}</div>
- 在JS中操作数据:
-
注意:
-
传入的数据可以是基本数据类型,也可以是复杂数据类型。
- 基本数据类型:底层依然是依靠
Object.defineProperty()的get()和set()实现的响应式。 - 复杂数据类型:底层帮你调用了vue3的一个新的函数——reactive函数。
- 基本数据类型:底层依然是依靠
-
3.reactive函数
-
作用:定义一个对象类型的响应式数据。可以接收一个对象或者数组类型的数据,返回值是一个Proxy实例对象。
-
用法:
//需要通过 `import {reactive} from 'vue'`进行引入才能使用。 let parson = reactive({ name:'张三', age:18 }) -
注意:
- reactive函数底层使用的是ES6中的Proxy函数实现的响应式。
- reactive函数定义的响应式数据是深层次的。
4. reactive与ref的区别
-
从定义数据角度:
- ref定义的是基本数据类型
- reactive定义的是复杂数据类型(对象和数组)
- 注意:ref也可以定义复杂数据类型的数据,内部会自动调用reactive函数。
-
从原理角度:
- ref使用
Object.defineProperty()的get()和set()实现的响应式(数据劫持)。 - reactive使用的是ES6中的Proxy函数实现的响应式(数据劫持),并通过Reflect函数来对源代码的属性进行操作。
- ref使用
-
从使用角度:
- ref:在JS中操作数据需要
.value,在模板中使用不需要。 - reactive: 可以直接使用,不需要
.value。
- ref:在JS中操作数据需要
5. Vue3的响应式原理
实现原理:
-
使用Proxy(代理)对数据的变化(添加、删除,修改、读取)进行监听。
-
使用Reflect(反射)对源对象进行操作。
- 好处:对比原生的对属性的操作,可以避免频繁报错,在封装框架时比较好,个人开发中不太需要。
let person = {
name: '李四',
age: 28
};
let p = new Proxy(person, {
get(target, prop) {
console.log(`读取了person的${prop}属性`)
return Reflect.get(target, prop)
},
set(target, prop, value) {
console.log(`修改了person的${prop}属性,我去更新页面了`)
Reflect.set(target, prop, value)
},
deleteProperty(target, prop) {
console.log(`我删除了person的${prop}属性`)
return Reflect.deleteProperty(target, prop)
}
})
6. 计算属性与监视属性
1. computed函数
-
作用:与 Vue2中一致.
-
用法:
import {computed} from 'vue' //计算属性简便写法 let name = computed(() => { return parson.xing + '-' + parson.ming }) //计算属性完整写法,挂载到parson上就不需要setup多返回一个数据了 parson.name = computed({ get() { return parson.xing + '-' + parson.ming }, set(value) { const arr = value.split('-') parson.xing = arr[0] parson.ming = arr[1] } })
2. Watch函数
-
作用:与Vue2中一致
-
注意点:
-
在监视ref定义的数据时:
- 基本数据类型时:不能使用
.value,因为加了过后是具体的基本类型值,是不能被监视的。 - 复杂数据类型时:需要使用
.value,因为底层求助了reactive函数,不加的话,监听的是对象的地址,加了过后监听的才是数据。(或者加deep也能解决)
- 基本数据类型时:不能使用
-
在监视reactive定义的数据时,oldValue(修改前的数据)无法正常获取。
- 原因:因为reactive定义的是复杂数据类型,所以修改前和修改后指针指向的是同一个对象。
-
在监视reactive定义的数据时,强制开启深度监视(deep配置失效,暂无解决方法)
-
3.用法:
-
情况1: 监视ref定义的响应式数据
let name = ref('张三') watch(name, (newValue, oldValue) => { console.log('name属性被修改了', newValue, oldValue) }, { immediate: true })- 输出:
-
情况2: 监视多个ref定义的响应式数据
let name = ref('张三') let age = ref(18) //监视多个数据时,传入的监视数据是一个数组 watch([name, age], (newValue, oldValue) => { console.log('name或者age属性被修改了', newValue, oldValue) }, { immediate: true })- 输出:
-
情况3: 监视reactive定义的数据
let parson = reactive({ name: '张三', frame: { frameName: 'Vue' } }) //注意点: // 1. deep配置无效 // 2.监听的是reactive定义的函数,oldValue无法正常获取,始终会显示为更新后的数据。 watch(parson, (newValue, oldValue) => { console.log('parson发生变化了', newValue, oldValue) }, { immediate: true, deep: false //已经强制开启了深度监视,此时的deep配置无效。 })- 输出:
-
情况4: 监视reactive定义的数据中的某一个属性
let parson = reactive({ name: '张三', frame: { frameName: 'Vue' } }) //注意点: // 1. 被监听的属性需要写成函数形式 // 2.因为此时监听的是复杂数据类型中的一个基本数据类型,所以oldValue可以正常获取 watch(() => parson.frame.frameName, (newValue, oldValue) => { console.log('parson的值发生变化了', newValue, oldValue) }, { immediate: true //因为是基本数据类型,所以不需要deep })- 输出:
-
情况5: 监视reactive定义的数据中的某些数据
let parson = reactive({ name: '张三', frame: { frameName: 'Vue' } }) //注意点: // 1. 监听多个数据时,需要写成数组形式,并且被监听的属性需要写成函数形式, // 2.因为此时监听的是复杂数据类型中的一个基本数据类型,所以oldValue可以正常获取 watch([() => parson.name, () => parson.frame.frameName], (newValue, oldValue) => { console.log('parson的值发生变化了', newValue, oldValue) }, { immediate: true })- 输出:
-
特殊情况:
- 监听reactive定义的响应式数据的子属性对象时,deep配置是有效的,不会强制开启深度监视。(只有监视的数据为 reactive声明的对象时才会强制开启深度监视,监听子属性不会。)
let parson = reactive({ name: '张三', frame: { frameName: 'Vue', firends: { name: '李四' } } }) watch(() => parson.frame, (newValue, oldValue) => { console.log('parson的值发生变化了', newValue, oldValue) }, { immediate: true, deep: true //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效 })- 输出:
3. watchEffect函数
-
作用:不需要指明监视哪个属性,只需要指定监视的回调函数,回调函数中用到了哪些属性,就会监视哪些属性。默认已经开启了
immediate. -
watchEffect与computed:
- watchEffect与computed比较类似,都是回调里用到过谁,就会监视谁。但是:
- computed注重结果,所以需要返回值。
- watchEffect注重过程,所以不需要返回值。
let x = ref(1)
let y = ref(2)
//只要回调函数里所用到的属性发生了变化,就会调用这个回调函数
watchEffect(() => {
let b = x.value + y.value;
console.log('watchEffect的回调函数调用了')
})
7. 生命周期
生命周期图(vue2和vue3)
-
注意点:
-
在Vue3中,有两个生命周期改名了:
beforeDestroy===>beforeUnmount销毁前改为解除绑定前destroyed===>unmount销毁改为解除绑定
-
在Vue3中,可以使用配置项的形式使用生命周期钩子,也可以使用Composition API 形式:
-
配置项形式
mounted(){ console.log('页面挂载了') } -
Composition API形式
import {onMounted} from 'vue' setup(){ onMounted(()=>{ console.log('页面挂载了') }) }
-
-
使用Composition API 形式时:
- 需要在原来的名字前加
on,并且需要引入。 beforeCreate()和Createed()在组合式API里不存在,直接使用setup即可
- 需要在原来的名字前加
-
8. 自定义hook函数
-
概念:本质是一个函数,把setup中使用的Composition API进行封装,类似于vue2中的mixin。
-
优势:代码复用,让setup中代码更少、更清晰。
-
用法:
-
在
usePoint.js中:import { reactive, onMounted, onBeforeUnmount } from 'vue' export default function () { let point = reactive({ x: 0, y: 0 }) function savePoint(event) { point.x = event.pageX point.y = event.pageY } //在页面挂载完毕时给window绑定点击事件 onMounted(() => { window.addEventListener('click', savePoint) }) //在页面解除挂载完毕时给window解除绑定点击事件 onBeforeUnmount(() => { window.removeEventListener('click', savePoint) }) //需要把point返回出去,否则组件中用不到。 return point } -
在组件中:
<template> {{point.x}},{{point.y}} </template> <script> import usePoint from './hooks/usePoint' export default { setup() { //调用hook函数并赋值。 let point = usePoint() return { point } } } </script>
-
toRef与toRefs
-
toRef概念:toRef可以创建一个ref对象,使其value值指向另一个对象的某个属性。
-
作用:可以使在模板中使用数据时少写一个前缀的变量名。
-
缺点:在setup返回数据时需要多次调用toRef函数,效率不高(使用toRefs可以解决)
-
用法:
<template> <!-- 原本调用数据的方式 {{obj.name}} {{obj.age}} --> <!-- 使用toRef后调用数据的方法 --> {{ name }} {{ age }} </template> <script> import { reactive, toRef } from '@vue/reactivity' export default { setup() { let obj = reactive({ name: '张三', age: 18 }) return { //原本返回数据的方法 //obj, //使用toRef后返回数据的方法 name: toRef(obj, 'name'), age: toRef(obj, 'age') } } } </script> -
toRefs概念:toRefs作用与toRef相同,但是可以一次性创建多个ref对象,提高效率。(只会返回对象的第一层数据)
-
用法:
<template> {{ name }} {{ age }} </template> <script> import { reactive, toRefs } from '@vue/reactivity' export default { setup() { let obj = reactive({ name: '张三', age: 18 }) return { //使用toRef返回数据的方法 //name: toRef(obj, 'name'), //age: toRef(obj, 'age'), //使用toRefs返回数据的方法,与拓展运算符结合可以解决toRef需要多次调用的痛点 ...toRefs(obj) } } } </script>
不常用的 Composition API
shallowReactive与shallowRef(浅响应式)
-
特点 :
- shallowReactive与reactive用法基本相同,但是shallowReactive只处理对象第一层的属性(浅响应式,类似于没开深度监视)
- shallowRef只处理基本数据类型的数据(与Ref效果相同),不会处理复杂数据类型的数据(不会把数据改成响应式)
-
运用场景:
- shallowReactive:一个对象只有外层数据需要响应式,深层次的数据不需要响应式时。
- shallowRef:当一个对象后期不需要修改,只需要一个新的对象替换时。
readonly与shallowReadonly(改为只读)
-
特点:
- readonly:使一个响应式数据变为只读的(深只读)。
- shallowReadonly:使一个响应式数据变为只读的(浅只读)。
-
优点:如果不把数据变为响应式,那么就会遇到当数据会被修改时,vue检测不到数据修改。这两个函数可以直接让数据不可修改,从根源上解决问题。
-
应用场景:不希望数据被修改时。
注意:这两个函数必须传入响应式数据。
toRaw和markRaw(转为普通对象)
-
toRaw:
-
作用:将一个由reactive生成的响应式对象变为普通对象。
-
使用场景:用于读取响应式对象对应的普通对象,对这个普通对象进行修改操作时,不会引起页面更新。
-
用法:
setup() { let obj = reactive({ name: '张三', age: 18, friend: { name: '李四' } }) let a = toRaw(obj) //此时的a是一个obj对应的普通对象,对其进行修改时不会引起页面更新。 console.log(a) return { obj, ...toRefs(obj) } }
-
-
markRaw:
-
作用:标记一个对象,使其永远不会再变为响应式对象。
-
应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类库等。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
-
用法:
setup() { let obj = reactive({ name: '张三', age: 18 }) //此时对添加到obj上的friend对象进行修改时,不会引起页面的刷新,因为friend对象不是响应式的 obj.friend = markRaw({ name: '李四' }) return { obj, ...toRefs(obj) } }
-
customRef(自定义Ref)
-
作用:customRef的作用是自定义一个Ref函数,可以使响应式数据的读取和修改时完成一些其他的操作。
官方理解为:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。
customRef本质是一个函数,需要接受一个回调函数,回调函数需要返回一个包含set和get方法的对象,回调函数接受参数为track, trigger。
-
使用方法:
function myRef(value) { return customRef((track, trigger) => { return { get() { track() //告诉vue这个value值时需要被追踪的 return value }, set(newValue) { value = newValue trigger() //告诉vue去更新页面 } } }) } -
定义一个Ref防抖函数
<template> <input type="text" v-model="str"> <h2>{{ str }}</h2> </template> <script> import { customRef } from '@vue/reactivity' export default { setup() { //定义一个自己的Ref函数 function myRef(str, times) { let timer; return customRef((track, trigger) => { return { get() { track() //告诉vue这个str值时需要被追踪的,否则返回的一直是初始化的值,不会更新DOM return str }, set(a) { clearTimeout(timer) timer = setTimeout(() => { str = a trigger() //告诉vue去更新页面 }, times) } } }) } //使用自定义Ref函数生成响应式数据 let str = myRef('hello', 500) return { str } } } </script>
provide与inject(祖孙通信)
-
作用:实现祖孙组件之间通信
-
套路:父组件有一个
provide选项来提供数据,后代组件有一个inject选项来开始使用数据只能父组件提供数据给后代组件使用。
提供的数据是响应式的,后代组件是可以修改这些数据的。
-
具体写法:
-
祖组件中:
setup(){ ...... let car = reactive({name:'奔驰',price:'40万'}) provide('car',car) ...... } -
后代组件中:
setup(){ ...... const car = inject('car') return {car} ...... }
-
响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly创建的只读代理 - isProxy: 检查一个对象是否是由
reactive或者readonly方法创建的代理
新组件
Teleport
-
作用:
Teleport是一种能够将我们的组件html结构移动到指定位置的技术。移动到的位置可以写html、body或者是CSS选择器
<teleport to="移动位置">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一个弹窗</h3>
<button @click="isShow = false">关闭弹窗</button>
</div>
</div>
</teleport>
Suspense(组件懒加载)
-
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
-
使用步骤:
-
异步引入组件
import {defineAsyncComponent} from 'vue' const Child = defineAsyncComponent(()=>import('./components/Child.vue')) -
使用
Suspense包裹组件,并配置好default与fallback<template> <div class="app"> <h3>我是App组件</h3> <Suspense> <template v-slot:default> <Child/> </template> <template v-slot:fallback> <h3>加载中.....</h3> </template> </Suspense> </div> </template>
-