vue3
main.js
import { createApp } from 'vue' //引入createApp工产函数
import App from './App.vue'
createApp(App).mount('#app')
setup
与vue2不同,vue3中的数据,方法都是写在setup函数中,通过return暴露出来
setup() {
let name = 'test';
function test() {
console.log('test');
};
return {
name,
test
}
}
setup不能是一个async函数,返回值不是return的对象而是promise,模板看不到return对象中的属性(若要使用async,需要Suspense和异步组件引入的配合)
ref函数(refImpl reference implementation参考实例)
- 作用:定义一个响应式的数据
- 语法:
const xxx = ref(initValue)
- 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)
- 改变该数据时用xxx.value
- 在模板中读取数据不需要加
.value
,直接{{xxx}}
即可
- 注:
- 接收的数据可以是基本类型,也可以是对象类型
- 基本类型的数据:响应式依然是靠
Object.defineProperty()
中的get
和set
实现 - 对象类型的数据:内部运用了vue3中的新函数——
reactive
函数
reactive函数
- 作用:定义一个对象类型的响应式数据(基本类型不要使用,用ref)
- 语法:
const xxx = reactive(源对象)
接收一个对象或数据,返回一个代理对象(proxy的实例对象,简称proxy对象) - reactive定义的响应式数据是深层次的
- 内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据
vue2和vue3响应式原理分析
vue2
- 实现原理:
- 对象类型:通过
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持) - 数组类型:通过重写更新数据的一系列方法来实现拦截(对数据的变更方法进行了包裹)
- 对象类型:通过
- 存在问题:
- 新增属性、删除属性,界面不会更新
- 直接通过下标修改数组,界面不会自动更新
vue3
实现原理:
- 通过Proxy(代理):拦截对象中任意属性的变化,包括属性值的读写、属性的添加、属性的删除等
- 通过Reflect(反射):对源对象的属性进行操作
- 实例:
const p = new Proxy(person, { get(target, propName){ console.log(`get,身上的${propName}属性`, target, propName); // return target[propName]; return Reflect.get(target, propName); }, set(target, propName, value) { console.log(`set,身上的${propName}属性修改为${value}`); // target[propName] = value; return Reflect.set(target, propName, value); }, deleteProperty(target, propName) { console.log(`delete,身上的${propName}属性删除`); // return delete target[propName]; return Reflect.deleteProperty(target, propName); } })
reactive对比ref
定义数据角度对比
- ref用来定义基本类型数据
- reactive用来定义对象(或数组)类型数据
- 注:ref也可以用来定义对象(或数组),会自动调用reactive
从原理角度对比
- ref通过
Object.defineProperty()
的get
和set
来实现响应式(数据劫持) - reactive通过Proxy来实现响应式,并通过Reflect来操作源对象内部的数据
从使用角度对比
- ref定义的数据:操作数据需要
.value
,读取数据时不用.value
- reactive定义的数据:直接操作和读取即可
setup的两个注意点
setup执行的时机
在beforeCreate之前执行一次,this是undefined
setup的参数
props:值为对象,包含外部传递过来且内部声明接收了的属性
context:上下文对象
- attrs:值为对象,包含组件外部传递过来但没有在props中配置声明的属性,相当于
this.$attrs
- slots:收到的插槽内容,相当于
this.$slots
- emit:分发自定义事件的函数,相当于
this.$emit
计算属性与监视
computed函数
- 与vue2中computed配置功能一致
- 写法:
setup() { let person = reactive({ firstName: '三', lastName: '张' }); //计算属性——简写(没有考虑计算属性被修改的情况) /* person.fullName = computed(()=>{ return person.lastName + '-' + person.firstName; }) */ //计算属性——完整写法 person.fullName = computed({ get() { return person.lastName + '-' + person.firstName; }, set(value) { const nameArr = value.split('-'); person.firstName = nameArr[1]; person.lastName = nameArr[0]; } }) return { person } }
watch函数
- 与vue2中watch配置功能一致
- 分为以下几种情况:
- 情况一:监视ref所定义的一个响应式数据
watch(sum, (newValue, oldValue)=>{ console.log('sum变化了', newValue, oldValue); }, {immediate: true})
- 情况二:监视ref所定义的多个响应式数据
watch([sum, msg], (newValue, oldValue)=>{ console.log('值发生改变', newValue, oldValue); }, {immediate: true})
- 情况三:监视reactive定义的响应式数据时,oldValue无法正确获取,强制开启deep深度监视(deep配置无效)
//此时deep无效 watch(person, (newValue, oldValue)=>{ console.log('person变化了',newValue, oldValue); }, {deep:false})
- 情况四:监视reactive所定义的一个响应式数据中的某个属性
watch(()=>person.name, (newValue, oldValue)=>{ console.log('person变化了',newValue, oldValue); })
- 情况五:监视reactive定义的响应式数据中的某个属性时,deep配置有效
//此时不配置deep无法获取数据变化 watch([()=>person.name, ()=>person.age], (newValue, oldValue)=>{ console.log('person的name或age变化了',newValue, oldValue); })
watchEffect函数
- watch:既要指明监视的属性,也要指明监视的回调
- watchEffect:不用指明监视哪个属性,监视的回调中用到哪个属性就监视哪个属性
- watchEffect有点像computed,computed注重的是计算出来的值,需要返回值;watchEffect注重的是过程,不用返回值
watchEffect(()=>{ const x1 = sum.value; const x2 = person.job.j1.salary; console.log('watchEffect执行了'); })
vue3生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4fXKFYNw-1658854391684)(https://v3.cn.vuejs.org/images/lifecycle.svg)]
自定义hook函数
- hook本质是一个函数,把setup函数中使用的Composition API进行了封装
- 类似于vue2中的mixin
- 自定义hook的优势:复用代码,让setup中的逻辑更加清晰易懂
toRef和toRefs
- 作用:创建一个ref对象,其value值指向另一个对象中的某个属性
- 语法:
const name = toRef(person, 'name')
- 应用:要将响应式对象中的某个属性单独提供给外部使用
- 扩展:
toRefs
与toRef
功能一致,但可以批量创建多个ref对象,语法:toRefs(person)
其他Composition API
shallowReactive和shallowRef
- shallowReactive:只处理对象最外层属性的响应式(浅响应式);若有一个对象数据,结构比较深,但变化时只是外层属性变化可以使用shallowReactive
- shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理;若有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换,使用shallowRef
readonly和shallowReadonly
- readonly:让响应式数据变为只读(深层次)
- shallowReadonly:浅层次只读
toRow和markRow
- toRaw:将一个由
reactive
生成的响应式对象转为普通对象;用于读取响应式对象对应的普通对象,对这个普通对象的所有操作不会引起页面更新 - markRaw:标记一个对象使之不会变成响应式对象;当有些值不应被设置为响应式或者渲染具有不可变数据源的大列表时,跳过响应式转换以提高性能
customRef
- 自定义ref,控制依赖项的更新触发
- 实现防抖效果:
<template>
<input type="text" v-model="keyWord">
<h3>{{keyWord}}</h3>
</template>
<script>
import { ref, customRef } from 'vue';
export default {
name: 'App',
setup() {
//自定义ref:myRef
function myRef(value, delay) {
return customRef((track, trigger) => {
let timer;
return {
get: function () {
console.log('myRef数据被读取');
track(); //通知vue追踪value的变化
return value;
},
set: function (newValue) {
console.log(`myRef数据被修改为${newValue}`);
clearTimeout(timer);
timer = setTimeout(() => {
value = newValue;
trigger(); //通知vue重新解析模板
}, delay);
}
};
});
}
// let keyWord = ref('hello');
let keyWord = myRef('hello', 500);
return { keyWord };
}
};
</script>
provide与inject
- 作用:实现祖孙组件间通信
- 父组件用
provide
来提供数据,后代组件用inject
来接收数据 - 使用:
- 父组件中:
import { provide } from 'vue';
setup() {
let car = { name: 'car', price: 100 };
provide('car', car); //第一个为自定义的名称,第二个为所提供的数据
}
- 子组件中:
import { inject } from 'vue';
setup() {
let car = inject('car');
return { car };
}
响应式数据的判断
- isRef:检查是否为ref对象
- isReactive:检查是否由
reactive
创建的响应式代理 - isReadonly:检查是否由
readonly
创建的只读代理 - isProxy:检查是否由
reactive
或者readonly
创建的代理(readonly创建的也为代理)
Composition API的优势
- 之前vue2的Option API中,若要新增或修改一个需求,要分别在data、methods和computed中进行修改
- 在vue3中,所有的配置放在setup中,可以更优雅的组织代码和函数,让相关代码更加直观有序
新的组件
Fragment
- 在vue2中必须要有跟标签
- 在vue3中可以没有跟标签,内部将多个标签包含在一个fragment虚拟元素中
- 好处是减少标签层级,减小内存占用
Teleport
可以将组件html结构移动到指定位置
<teleport to='body'>
<div class="mask" v-if="isShow">
<div class="dialog">
<h3>弹窗</h3>
<h4>内容</h4>
<button @click="isShow = false;">关闭</button>
</div>
</div>
</teleport>
Suspense
- 作用:等待异步组件时渲染一些额外内容
- 使用:
- 异步引入组件(defineAsyncComponent)
import { defineAsyncComponent } from 'vue'; const Son = defineAsyncComponent(() => import('./components/Son.vue')); //异步引入
- 使用
Suspense
包裹组件,并配置好v-slot:default
和v-slot:fallback
<div class="app"> <h3>我是app</h3> <Suspense> <template v-slot:default> //默认展示的 <Son></Son> </template> <template v-slot:fallback> //default展示出来前展示 <h3>just wait a minute</h3> </template> </Suspense> </div>
其他
全局API的转移
vue2全局API(Vue) | vue3全局API(app) |
---|---|
Vue.config.xxx | app.config.xxx |
Vue.config.productionTip | 移除 |
Vue.component | app.component |
Vue.direct | app.direct |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |
其他改变
- data选项应始终声明为函数形式
- 过渡类名的改变
vue2 vue3 .v-enter .v-enter-from .v-enter-to .v-enter-to .v-leave .v-leave-from .v-leave-to .v-leave-to - 移除keyCode作为v-on的修饰符,同时也不再支持
config.keyCodes
(自定义别名按键) - 移除
v-on.native
修饰符(子组件中使用的从父组件接收的自定义事件需要emits:['xxx']
进行声明) - 移除过滤器filter
- …