vue3的新特性总结,针对有vue2基础人群
介绍
vue3最大的改变就是组合式API,我们在setup中可以分功能,分模块写代码(类似vue2的minxis), 不像vue2,把所有数据都丢在data中, 把所有的方法都丢在methods中等等, 代码量多了, 我们要找一个功能模块的代码很难.
setup的使用以及注意事项
* 1.setup在创建组件之前执行
* 2.setup选项中没有this
* 3.setup的参数(props,context)
注意:vue2配置中(data,methods,computed…)可以访问到vue3的setup中的属性和方法,但是setup里面不能访问到vue2中的配置,如果有重名,setup优先,所以,vue3和vue2尽量不要混用。
setup参数解析:props,context
a. props 是响应式的,你不能使用 ES6 解构,因为它会消除 prop 的响应性。
如果需要解构 prop,可以通过使用 setup 函数中的 toRefs 来安全地完成此操作
setup(props) {
//这里先不要管里面的语法,后面会介绍
const { title } = toRefs(props)
console.log(title.value)
}
b. context 是一个普通的 JavaScript 对象(它不是响应式的,可以解构),它暴露三个组件的 property
setup(props, context) {
// Attribute (非响应式对象)
console.log(context.attrs)
// 插槽 (非响应式对象)
console.log(context.slots)
// 触发事件 (方法)
console.log(context.emit)
}
常用Api
1.ref:定义一个响应式数据
ref对我们的值创建了一个引用对象,响应式(ref用来定义响应式的 字符串、 数值、 数组、Bool类型)
let msg = ref("这是setup中的msg"); setup中使用时:msg.value(注意:模板中会自动解析,不用加.value)
注意:
- ref可以接收基本数据类型,也可以接收引用类型
- ref处理基本数据类型的响应式时的原理(defineProperty的getter和setter),但处理复杂类型引用了下面的reactive(原理:proxy)
2.reactive: 定义一个对象类型的响应式数据
用来定义响应式的对象,使用时无需.value
let setupData = reactive({
title: "reactive定义响应式数据的title",
userinfo: {
username: "张三",
age: 20
}
})
3.toRefs与toRef
解构响应式对象数据
//toRef
setup() {
const user = reactive({
username: "张三",
age: 10000,
});
return {
//toRef(拿值的对象,拿值的对象的具体属性) 特点:一次拿一个
username:toRef(user,'username')
};
},
//toRefs
setup() {
const user = reactive({
username: "张三",
age: 10000,
});
return {
//toRefs(拿值的对象) 特点(可直接拿一整个对象,其中的属性都是refImpl对象)
...toRefs(user)
};
},
// 模板中可以直接使用username和age
4.readonly
“深层”的只读代理
const original = reactive({ count: 0 });
const copy = readonly(original);
copy为只读,不能修改
5.setup中computed的使用
setup(){
let person = resctive({
firstName:'谢',
lastName:'小雨'
})
//computed的简写(只能读取,不能修改)
let person.finName = computed(()=>{
return person.firstName+person.lastName
})
//computed的完整写法(可以读取,可以修改) eg:input框中为计算的属性,可以修改 123-456789
let person.finName = computed(()=>{
get(){
return person.firstName+'-'+person.lastName
}
set(value){
let name = value.split('-')
person.firstName = name[0]
person.lastName = name[1]
}
})
return {
persion
}
}
6.setup中watch的使用
watchEffect
import {watchEffect,watch} from 'vue'
setup(){
let lastName = ref('三')
let firstName = ref('张')
let person = reactive({
name:'李四',
age:18,
job:{
xinzi:'1k',
}
})
//watch(监视的属性,监视的回调,[参数配置(例如:{immediate:true})])
//情况一:监听一个ref数据
watch(firstName,(newVal,oldVal)=>{
})
//情况二:监听多个ref数据
watch(lastName,(newVal,oldVal)=>{
})
watch(lastName,(newVal,oldVal)=>{
})
//或者简写为(监听的值为数组)
watch([firstName,lastName],(newVal,oldVal)=>{
console.log(newVal,oldVal)//这时的newVal和oldVal为数组 [firstName的 NewVal,lastName的 NewVal],[firstName的oldVal,lastName的oldVal]
//如果一个改变一个没有改变,则没有改变的NewVal = oldVal
})
//情况三:监听一个reactive数据,
//注意:
//1.此处oldVal无法正确获取(引用数据)
//2.无论对象中属性的层次有多深,都可以监听到(就像vue2中开启了deep:true)
watch(person,(newVal,oldVal)=>{
console.log(newVal)
console.log(oldVal)//oldVal与newVal一样
})
//情况四:监听一个reactive数据中的某一个属性,
//注意:
//1.此处的监听的值若直接写成person.age,那就是监听18,不正确(监听的值必须为ref属性)
//2.无论对象中属性的层次有多深,都可以监听到(就像vue2中开启了deep:true)
watch(()=>person.age,(newVal,oldVal)=>{
console.log(newVal)
console.log(oldVal)
})
//情况五:监听一个reactive数据中的某些属性,
watch([()=>person.age,()=>person.name],(newVal,oldVal)=>{
console.log(newVal)
console.log(oldVal)
})
//watchEffect(监视的回调)监视回调中用到哪个变量就监听哪个变量
watchEffect(()=>{
let name = lastName.value + firstName.value
})
}
7.shallowReactive和shallowRef
shallowReactive:只处理对象最外层的响应式(浅响应式)
shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理 (基本数据类型时.与ref作用相同)
使用场景:
- 一个对象数据,结构比较深,但变化时只是外层属性变化 ===> shallowReactive
- 一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef
8.readonly和shallowReadonly
readonly(接收一个响应式数据)
shallowReadonly(接收一个响应式数据) (只影响最外层)
使用场景:
- 使用别人定义号的组件时,定义的有些变量不能更改,不然别人的地方回受影响,这时,我们可以用readonly()的返回值,控制台会报警告,修改不了值.
import {ref,reactive,readonly,shallowReadonly} from 'vue'
setup(){
let person = reactive({
name:'张三',
age:18,
})
person = readonly(person) //person中的任何属性都不能更改
person = shallowReadonly(person) //person中的最外层属性不能更改
}
9.toRow和markRow
toRow(接收一个由reactive生成的响应式对象) 响应式对象转换为普通对象
使用场景:
- 读取响应式对象的普通对象,对这个普通对象的所有操作,页面不会更新.
markRow(对象) 标记一个对象,使其永远不会成为响应式对象
使用场景:
-
有些值不应被设置为响应式(如:第三方插件库等).
-
当渲染具有不可变数据源的大列表时(如:一个非常复杂,层次很深的对象),跳过响应式转换可以提高性能
import {toRaw,markRow,reactive} from 'vue'
setup(){
let person = reactive({
name:'张三',
age:18,
})
let car = {
...
}
person = toRaw(person) //person中的任何属性都不能更改
person = markRow(car) //person中的最外层属性不能更改
}
10.customRef自定义ref
html场景:input框输入什么,h3隔1s后展示输入的值
<input v-model="keywords">
<h3>{{keywords}}</h3>
import {customRef,ref} from 'vue'
setup(){
//自定义一个ref
function myRef (value){
let timer //定义一个定时器
//customRef的参数:
//track:通知vue去追踪数据的改变
//tirgger:通知vue去重新解析模板
return customRef((track,trigger)=>{
get(){
track() //通知vue去追踪value的改变
return value
}
set(newValue){
clearTimeOut(timer)
timer=setTimeOut(()=>{
value = newValue
trigger() //通知vue去重新解析模板
},1000)
}
})
}
let keywords = myRef('hello')
return {
keywords
}
}
11.provide与inject
//父
setup(){
let person = reactive({
name:'张三',
age:12
})
provide('person',person)
}
//子
setup(){
//也可以使用,但一般不这么用
}
//孙
setup(){
let x = inject('person')
//所有后背都可以通过inject拿到provide定义的值
}
12.响应式数据的判断
- isRef:判断一个值是否为ref对象
- isReactive:判断一个对象是否为reactive对象
- isReadonly:判断一个对象是否由readonly创建的只读代理
- isProxy:判断一个对象是否为由reactive或readonly创建的代理
vue3的生命周期
与vue2的不同之处:
- beforeDestory ===> beforeUnMount
- destoryed ===> unMounted
组合api形式的生命周期使用:
import {onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnMount,onUnMounted} from 'vue'
setup(){
//setup中没有beforeCreat和created的组合式api,因为setup就相当于beforeCreat和created的组合式api
onBeforeMount(()=>{
}),
onMounted(()=>{
}),
onBeforeUpdate(()=>{
}),
onUpdated(()=>{
}),
onBeforeUnMount(()=>{
}),
onUnMounted(()=>{
}),
}
vue3自定义hook函数
什么是hook: 本质是一个函数 (就是把setup里面使用的组合式api封装到其他js文件,使代码复用,类似vue2的mixin),这里就不放代码了,可以自己试试.
vue3新特性
1.teleport传送门组件
直接传送指定的父元素下
<teleport to="body">
...需要传送的内容
<div>hahahahah</div>
</teleport>
//例如:to属性,指定父元素
2.emits选项
自定义事件需要定义在emits选项中
export default {
emits:['change']
}
好处:
a.如果和原生事件重名(如:click),声明emits选项后,只会触发一次emits事件,如果不声明,会触发2次
b.更好的指示组件的工作方式
c.对象形式校验(自行看文档)
suspense和异步组件
//父组件
<template>
<suspense>
//默认加载
<template v-slot:default>
<Child/>
</template>
//组件还没有加载出来时展示的内容
<template v-slot:fallback>
<h3>正在加载,请稍后...</h3>
</template>
</suspense>
<Child/>
</template>
//异步引入组件
//配合suspense的使用
import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import(./components/Child))
//子组件
async setup(){
let sum = ref(10)
let p = new Promise((res,rej)=>{
setTimeOut(()=>{
res({sum})
},1000)
})
return await p
}
//或者这样写
//子组件
setup(){
let sum = ref(10)
return new Promise((res,rej)=>{
setTimeOut(()=>{
res({sum})
},1000)
})
}
其他不同之处
1.全局的api改为实例的方法
vue2中有很多全局的方法(如:Vue.component,Vue.filter,Vue.directive,Vue.delete,Vue.set,Vue.nextTick,Vue.extend等)
vue2中使用 new Vue() 作为app,这样的话所有的创建的根实例都会共享相同的根实例(测试时会污染其他测试用例)
vue3使用 creatApp() 返回app实例
2.x全局API | 3.x实例API(app) |
---|---|
Vue.config.xxxx | app.config.xxxx |
Vue.config.productionTip | 移除 |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |
vue3的摇树优化
vue2中的一些没有用到的全局方法会静态挂载到vue的构造函数上(eg:Vue.nextTick),我们从来没使用过,这称之为 ‘dead code’ ,这类代码无法使用webpack的tree-shaking排除掉(也就是说:依然会打包)
import Vue from 'vue'
//使用
Vue.nextTick(()=>{
//
})
vue3中把他们拆分成了独立函数,这样 ‘dead code’ 这类代码就可以使用webpack的tree-shaking排除掉
import {nextTick} from 'vue'
nextTick(()=>{
//
})
受影响的api:
- Vue.nextTick
- Vue.observable(replaced by Vue.reactive)
- Vue.version
- Vue.compile
- Vue.set
- Vue.delete
v-model的变化
vue2中的v-model与.sync功能有重叠,容易混,vue3做了统一
vue3移除了.sync,v-model的使用有点变化:(写法更像sync)
//父组件:
<template>
<div>
<clide v-model="data"></clide> (相当于vue2版本的写法:<clide :modelVaule="data" @update:modelValue="data=$event"></clide>)
</div>
</template>
//子组件
<div @click="changeValue">{{modelValue}}</div>
...
props:['modelValue']
...
changeValue(){
let newVlaue = 3
this.$emit('update:modelValue',newVlaue)
}
如果要自定义接收的值:
//父组件:
<template>
<div>
<clide v-model:num="data"></clide> (相当于vue2版本的写法:<clide :num="data" @update:num="data=$event"></clide>)
</div>
</template>
//子组件
<div @click="changeValue">{{modelValue}}</div>
...
props:['num']
...
changeValue(){
let newVlaue = 3
this.$emit('update:num',newVlaue)
}
渲染函数的变化
vue3中的rander函数:
//插槽的使用
<RanderTest>
<template v-slot:title>title</template>
<template v-slot:content>content</template>
</RanderTest>
import {h} from 'vue'
components:{
RanderTest:rander(){
//插槽内容的获取
//vue2:this.$scopeSlots.title()
//vue3 统一slots
this.slots.title() //获取具名插槽
this.slots.default() //获取默认插槽
return h('div',[
h('div',{
onClick(){
//注意里面的this环境
}
},'woshi div'),
h('button',{
})
])
}
}
}
注意
vue3中的data必须是函数
vue3移除keyCode的修饰符,移除native修饰符(因为vue3有emits配置项)
vue3移除filter过滤器
o n , on, on,once,$off被移除