Vue3
本篇主要总结了vue3的知识与概念
一.常用Composition API (组合式API)
1.1拉开序幕的setup
1.是什么?是vue3中一个新的配置项,值为一个函数
2.理解:是所有组合式API"表演的舞台"
3.怎么使用? 组件中所用到的:数据,方法等等,均要配置在setup中
4.返回值:setup函数的两种返回值,一个是对象,第二个就是返回一个渲染函数
a.若返回一个对象,则对象中的属性,方法,在模板中均可使用(重点)
b.若返回的是一个渲染函数:则可以自定义渲染内容(了解)
5.注意点:
a. 尽量不要和vue2配置混用
vue2配置(data,methods,computed...)中可以访问到setup中的属性与方法但在setup中不能访问到vue2配置的(data,methods,computed...)如果有重名,setup优先
b. setup不能是一个async函数,因为返回值不再是return的对象,
而是promise,模板看不到return对象中的属性。
(注意:后期也可以使用返回一个Promise实例,但是需要Suspense和异步组件配合)
setup参数
参数:props:父组件传过来的,值为对象,包含:组件外部传过来,且组件内部声明接收了的属性
context: 上下文对象
| emit | 可以触发父亲的自定义事件 |
|---|---|
attrs |
非props属性组成的对象(捡漏) |
slots |
插槽信息组成的对象 |
说一下你对Vue3的了解
性能更高了
1.响应式原理换成了proxy
2.VNode Diff的算法进行了优化
体积更小了
1.删除了一些不常用的API,例如filter,EventBus...
2.所以API都是按需引入,能配合webpack等构建工具支持Tree Shaking
对TS支持更好了 源码就是使用TS写的
Compostition API(组合API)
1.解决了同一功能的数据和业务逻辑复用的问题
2.Vue2确实可以通过mixin进行复用,
但是mixin容易造成命名冲突和数据来源不清晰等问题
新特性
1.Fragment
2.Teleport
3.Suspense
……
1.2 ref函数
作用:定义一个响应式数据
语法:
//第一步:按需导入ref函数
import {ref} from 'vue'
//第二步:在setup配置项中使用
const xxx = ref('666')
/*
创建了一个包含响应式数据的引用对象(reference对象,简称ref对象)
js中操作数据:xxx.value
模板中读取数据:不需要使用.value 直接:{{xxx}}
*/
备注:
1.接收的数据可以是:基本数据类型,也可以是对象类型
2.基本数据类型的数据:
响应式依然依靠Object.defineProperty()的getter与setter完成的,
他只能读取,不能新增和删除
3.对象类型的数据:
内部使用了vue3中的一个新函数—reactive函数(内部封装es6新增的Proxy的操作)
4.ref包裹的内部数据,如果说是一个复杂数据类型,其实他还是一个reactive
1.3 reactive函数
1.作用:定义一个对象,数组类型的响应式数据(基本数据类型别用他,用ref函数)
2.语法:
const 代理对象名 = reactive(源对象),接收一个对象(或数组),
返回一个代理对象(Proxy的实例对象,简称proxy对象)
//第一步:按需导入ref函数
import {reactive} from 'vue'
//第二步:在setup配置项中使用
const 对象名 = reactive({属性名:属性值})
//直接读取,别写.value了
console.log(对象名.属性名)
3.reactive定义的响应式数据是’深层次的’
4.内部是基于ES6的Proxy实现,通过代理对象操作源对象内部数据都是响应式的
5.reactive包裹的内部数据,如果说是一个复杂数据类型,其实他还是一个reactive
1.4 Vue3中的响应式原理
vue2的响应式
实现原理:
对象类型:通过Object.defineProperty()对属性的读取,修改进行拦截(数据劫持)
数组类型:通过重写更新数组的一系列方法来实现拦截(对数组的变更方法进行了包裹)
let p = {}
Object.defineProperty(给谁添加属性,'新增属性的名字',{
//有人读取新增属性的时候调用
get(){
return 修改后的值 //修改后的值会传给set的形参
},
//有人修改新增属性时调用
set(value){}
})
//get,set只能读取,无法捕获到新增和删除,因此在vue中,
//新增,删除操作没有响应式,这就是单向数据流为啥产生的原因
存在的问题:
1.新增属性,删除属性,界面不会更新
this.$set(添加谁,’属性名’,’属性值’) Vue.set(添加谁,’属性名’,’属性值’)
this.$delete(删除谁,’属性名’) Vue.delete(删除谁,’属性名’)
2.直接通过下标修改数组,界面不会自动更新
vue3的响应式
实现原理:
步骤1:使用Proxy代理对象完成:通过它去拦截对象中任意属性的变化,包括属性的读写,增删
const p = new Proxy(给谁绑定,{
//3个方法get set deleteProperty
//有人读取p的某个属性
/*
target 就是目标源 也就是给谁绑定的对象
propName 就是增删改查的哪个属性
value 就是你修改过后的值
*/
get(target,propName) {
return target[propName]
},
//有人修改了p的某个属性,或给p追加某个属性时调用
set(target,propName,value){
target[propName] = value
},
//有人删除了p的某个属性
deleteProperty(target,propName){
return delete target[propName]
}
})
步骤2:使用Reflect反射对象完成:对源对象的属性进行操作
配合window的内置对象Reflect 反射 ES6新增的一个内置对象
除了原始的增删改查,还能使用Reflect内置对象
//查
Reflect.get(从哪个对象身上得到,'得到哪个属性')
//改 增
Reflect.set(从哪个对象身上改,'改哪个属性','改成什么')
Reflect.set(从哪个对象身上增,'增哪个属性','添加什么')
//删
Reflect.deleteProperty(从哪个对象身上上,'删哪个属性')
Proxy配合Reflect实现vue3的响应式
const p = new Proxy(给谁绑定,{
//3个方法get set deleteProperty
//有人读取p的某个属性
/*
target 就是目标源 也就是给谁绑定的对象
propName 就是增删改查的哪个属性
value 就是你修改过后的值
*/
get(target,propName) {
return Reflect.get(target,propName)
},
//有人修改了p的某个属性,或给p追加某个属性时调用
set(target,propName,value){
Reflect.set(target,propName,value)
},
//有人删除了p的某个属性
deleteProperty(target,propName){
return Reflect.deleteProperty(target,propName)
}
})
1.5 reactive和ref
从定义数据角度对比:
1.ref用来定义:基本数据类型
2.reactive用来定义:对象和数组类型的数据
备注:ref也可以用来定义对象(或数组)类型数据,它内部会自动通过reactive转化为代理对象
从原理角度对比:
1.ref通过Object.defineProperty()的get和set实现响应式(数据劫持)
2.reactive是通过Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据
从使用角度对比:
1.ref定义的数据:操作数据需要 .value ,读取数据时,模板中直接读取不需要 .value
2.reactive定义的数据:操作数据和读取数据,均不需要 .value
1.6计算属性与监视
计算属性
与vue2中computed配置功能一致
语法:
//vue3中一定要按需导入computed,才能使用计算属性
import {computed} from 'vue'
export default {
name:"demo",
setup() {
let p = reactive({
first = 'a',
last = 'b'
})
//这里的full在页面上能直接使用,p.full
person.full = computed(()=>{
get() {
return p.first + '-' + p.last
},
set(val){
p.first = val.split('-')[0]
p.last = val.split('-')[1]
}
})
//搞完以后一定要返回出去
return {
p
}
}
}
watch函数
与vue2中watch功能一致
语法:
//vue3中一定要按需导入watch,才能使用计算属性
import {watch} from 'vue'
export default {
name:"demo",
setup() {
let sum = ref(0)
let msg = ref('你好')
/*
监视ref定义的一个响应式数据
watch(你要监视谁,(newVal,oldVal)=>{
console.log('sum改变了',newVal,oldVal)
})
watch(sum,(newVal,oldVal)=>{
console.log('sum改变了',newVal,oldVal)
})
*/
/*
监视ref定义的多个响应式数据
此时newVal,oldVal返回的是一个数组
watch([sum,msg],(newVal,oldVal)=>{
console.log('sum改变了',newVal,oldVal)
},{immediate:true,deep:true})
*/
//搞完以后一定要返回出去
return {
sum,
msg
}
}
}
1.1监视ref定义的一个响应式数据
watch(sum,(newVal,oldVal)=>{
console.log('sum改变了',newVal,oldVal)
})
1.2监视ref定义的多个响应式数据
此时newVal,oldVal返回的是一个数组
watch([sum,msg],(newVal,oldVal)=>{
console.log('sum改变了',newVal,oldVal)
},{immediate:true,deep:true})
2.监视reactive定义的数据
注意:
1.如果监视的是reactive定义的响应式数据时(整个对象,或者使用ref传递的数据是对象),
此处无法正确的获取oldValue,并且强制开启了深度监视(deep配置无效)
2.如果监视的是reactive定义的响应式数据中的某个属性,这个属性是基础数据类型,但是需要把数据写成函数返回的代码,例如watch(()=>对象.属性,(newval,oldval)=>{})
3.如果监视的是reactive定义的响应式数据中的某些属性,这个属性是基础数据类型,但是需要把数据写成函数返回的代码,
例如:watch([()=>对象.属性1,()=>对象.属性2],(newval,oldval)=>{})
4.如果监视的是reactive定义的响应式数据中的某个属性的值依然是一个对象,
这个属性是复杂数据类型,且deep有效
总结:监听对象类型的数据是拿不到oldVal的
//vue3中一定要按需导入watch,才能使用计算属性
import {watch} from 'vue'
export default {
name:"demo",
setup() {
let p1 = ref({
name:666
})
let p = reactive({
name:"张三",
age:18
})
watch(p1.value,(newVal,oldVal)=>{
console.log('p1改变了',newVal,oldVal)
})
watch(p,(newVal,oldVal)=>{
console.log('p改变了',newVal,oldVal)
})
//搞完以后一定要返回出去
return {
p,
p1
}
}
}
3.对于ref在watch中是否.value去监视
1.如果是ref定义的基本数据类型不要去.value,
如果你去.value,那么它侦听的就不是你的属性了,而是你属性的value值
2.如果是ref定义的复杂数据类型要去.value,
因为你ref里面写对象,本质上还是要拿到他的reactive对象
3.如果是ref定义的复杂数据类型,你不想写.value,
则只需要开启深度监视即可
watchEffect函数
官方解释:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
watch的套路是:既要指明监视的属性,也要指明监视的回调
watchEffect的套路是:不管指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性
watchEffect有点像computed
但是computed注重的计算出来的值(回调函数的返回值),所以必须写返回值
而watchEffect更注重过程(回调函数的函数体),所以不用写返回值
1.7 生命周期
Vue3中可以继续使用Vue2的生命钩子,但是有两个被更名:
| beforeDestroy | 改为 | beforeUnmount |
|---|---|---|
| destroyed | 改为 | unmounted |
Vue3.0也提供了组合式API形式的生命周期钩子,即写在setup中的钩子,与Vue2.0中钩子对应关系如下
| Vue2.0 | 改变 | Vue3.0 |
|---|---|---|
| beforeCreate | setup | |
| created | setup | |
| beforeMount | onBeforeMount | |
| mounted | onMounted | |
| beforeUpdate | onBeforeUpdate | |
| updated | onUpdated | |
| beforeDestroy | onBeforeUnmount | |
| destroyed | onUnmounted |
语法:生命周期(()=>执行代码)
1.8 自定义hook函数
1.什么是hook?本质是一个函数,把setup函数中使用的组合API进行了封装
2.类似于vue2中的mixin
优势:复用代码,让setup中的逻辑更清除易懂
1.9 toRef
作用:创建一个ref对象,其value值指向另一个对象中的某个属性值
语法:const name = toRef(操作的对象,'属性')
应用:要将响应式对象中的某个属性单独提供给外部使用
扩展:toRefs与toRef功能一致,但是可以批量创建多个ref对象,语法:toRefs(操作对象)
二.其他的组合API(了解)
1. shallowReactive和shallowRef
shallowReactive:只处理对象最外层属性的响应式(只考虑第一层)
shallowRef:只支持处理基本数据类型的响应式,不进行对象响应式处理
什么时候使用?
shallowReactive:如果有一个对象数据,结构比较深,但变化时只是外层属性变化
shallowRef:如果有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换
2. readonly和shallowReadonly
readonly:让一个响应式数据变为只读的(深只读)
shallowReadonly:让一个响应式数据变为只读的(浅只读)
应用场景:不希望数据被修改时
3. toRaw和markRaw
toRaw:
作用:将一个由reactive生成的响应式对象转换为普通对象
使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所以操作,不会引起页面刷新
markRaw:
作用:标记一个对象,使其永远不会再成为响应式
应用场景:
1.有些值不应该被设置为响应式的,例如第三方库等
2.当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
4. customRef
作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显式控制
看官网:customRef
track 通知vue追踪数据的变化
trigger 通知vue再次解析模板
5. provide/inject
作用:实现祖与后代通信
使用:
发:provide(‘叫啥’, 数据变量名)
收:inject(‘叫啥’)
两个叫啥必须一致,可以随便叫什么名字
孙传爷
思路:在爷爷中封装一个函数,并且将这个函数通过provide(‘叫啥’, 数据变量名)发送过去,
后代通过inject(‘叫啥’)接收,拿到函数后,找到需要时进行调用并传参
6.响应式数据的判断
isRef:检查一个值是否为ref对象
isReactive:检查一个对象是否由reactive创建的响应式代理
isReadonly:检查一个对象是否是由readonly创建的只读代理
isProxy:检查一个对象是否由reactive或者readonly方法创建的代理
三.组合API的优势
1.解决了同一功能的数据和业务逻辑复用的问题
2.Vue2确实可以通过mixin进行复用,但是mixin容易造成命名冲突和数据来源不清晰等问题
四.新的组件
Fragment组件
在vue2中:组件必须有一个根标签
在vue3中,组件可以没有根标签,内部会将多个标签包含着一个Fragment虚拟元素中
好处:减少标签层级,减少内存占用
Teleport组件
是什么?Teleport是一种能够将我们的组件html结构移动到指定的位置的技术
<template>
<div class="child">
<dialog v-if="bBar" />
<button @click="handleDialog">显示弹框</button>
</div>
</template>
<script>
import { ref } from 'vue'
import Dialog from './Dialog.vue'
export default {
name: 'Child',
components: {
Dialog,
},
setup() {
const bBar = ref(false)
const handleDialog = () => {
bBar.value = !bBar.value
}
return {
bBar,
handleDialog,
}
},
}
</script>
解决
<template>
<div class="child">
<teleport to="body">
<dialog v-if="bBar" />
</teleport>
<button @click="handleDialog">显示弹框</button>
</div>
</template>
Suspense组件(配合异步组件使用)
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
<Suspense>
<异步组件名/>
<!-- 在 #fallback 插槽中显示 “正在加载中” -->
<template #fallback>
Loading...
</template>
</Suspense>
在上方存放异步组件,在#fallback中存放异步组件未加载出来时的页面,
当上方的异步组件未加载出来时,让他先加载下面的页面
五.其他
1.全局API的转移
vue2有许多全局的API和配置,例如注册全局组件,注册全局指令等
vue3中对这些API做出了调整:
即:Vue.xxx调整到应用实例(app)上
| 2.x 全局API(Vue) | 3.x 实例API(app) |
|---|---|
| Vue.config.xxx | app.config.xxx |
| Vue.config.ProductionTip | 移除 |
| Vue.component | app.component |
| Vue.directive | app.directive |
| Vue.mixin | app.mixin |
| Vue.use | app.use |
| Vue.prototype | app.config.globalProperties |
2.其他改变
1.data选项应该始终被声明为一个函数
2.过渡类名的更改
3.移除了keyCode作为v-on的修饰符,同时也不再支持config.keyCodes
4.移除v-on.native修饰符
5.移除过滤器filter