vue2和vue3的比较
vue3更快
- diff方法优化:
Vue2中的虚拟dom是进行全量的对比,Vue3新增了静态标记(PatchFlag)在与上次虚拟节点进行对比时候,只对比带有patch flag的节点并且可以通过flag的信息得知当前节点要对比的具体内容
2. hoistStatic静态提升
- Vue2中无论元素是否参与更新,每次都会重新创建
- Vue3中对于不参与更新的元素,只会被创建一次,之后会在每次渲染时候被不停的复用
3. cacheHandlers事件侦听器缓存
- 默认情况下onClick会被视为动态绑定,所以每次都会去追踪它的变化,但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可。
Options API 和 Composition API 的区别
Options API
又叫选项 API,以vue为后缀的文件,通过定义methods,computed,watch,data等属性与方法;
优缺点
条例清晰,相同的放在相同的地方;但随着组件功能的增大,关联性会大大降低,组件的阅读和理解难度会增加;
调用使用this,但逻辑过多时this会出现问题,比如指向不明等;
其本身并不是有效的js代码 我们在使用options API 的时候,需要确切了解我们具体可以访问到哪些属性,以及我们访问到的当前属性的行为在后台,VUE需要将此属性转换为工作代码,因为 我们无法从自动建议和类型检查中受益,因此给我们在使用相关属性时,造成了一定弊端
Composition API
又叫组合式API,组件根据逻辑功能来组织的,一个功能所定义的所有 API 会放在一起(更加的高内聚,低耦合)
即使项目很大,功能很多,我们都能快速的定位到这个功能所用到的所有 API;
优势 :
其代码更易读,更易理解和学习,没有任何幕后操作
Composition API的好处不仅仅是以不同的方式进行编码,更重要的是对于代码的重用
不受模板和组件范围的限制,也可以准确的知道我们可以使用哪些属性
由于幕后没有什么操作,所以编辑器可以帮助我们进行类型检查和建议
vue3结构
入口文件
//引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import { createApp } from 'vue'
import App from './App.vue'
//创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更“轻”)
const app = createApp(App)
//挂载
app.mount('#app')
初识setup
定义:setup函数是所有的组合API的入口函数,组件中用到的数据、方法等,均要配置到setup函数中。
- 普通数据: 定义数据直接用JS的方式定义(但不是响应式)。
- 方法: 方法直接定义
- 响应式数据: 引入使用ref,通过
ref函数定义数据,使数据变为响应式 dom和script想要使用setup中的数据和方法必须在setup中通过return以对象的方式暴露出去。
setup的返回值:
- 若返回一个对象,则对象中的属性、方法,在模板中均可使用。
- 若返回一个渲染函数,则可自定义渲染内容(render h 函数)。
setup的两个注意点
- setup执行的时机
- 在beforeCreate之前执行一次,this是undefined。
- setup的参数
- props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性(和vue2一样声明接收)。
- context:上下文对象
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
this.$attrs(在父组件中传了但是在子组件中没有用props接收,就会挂载到子组件的vm上的attrs上就没了,捡漏的)。 - slots: 收到的插槽内容, 相当于
this.$slots(父组件的插槽,以数组的形式存储)。 - emit: 分发自定义事件的函数, 相当于
this.$emit,在vue3中需要用context.emit(),而且需要在子组件中注册在父组件中需要触发的事件,emits,否则出现警告。
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
export default {
name: 'Demo',
props:['msg','school'],
emits:['hello'],
setup(props,context){
// console.log('---setup---',props)
// console.log('---setup---',context)
// console.log('---setup---',context.attrs) //相当与Vue2中的$attrs
// console.log('---setup---',context.emit) //触发自定义事件的。
console.log('---setup---',context.slots) //插槽
//数据
let person = reactive({
name:'张三',
age:18
})
//方法
function test(){
context.emit('hello',666)
}
//返回一个对象(常用)
return {
person,
test
}
}
}
注:
- 不能用
async修饰setup,否则setup的返回值为 promise,导致数据和方法暴露不出去。 - setup函数中 的 this 默认为 undefined。
vue2方式和vue3方式的混合
- vue2 可以用this的方式拿到 vue3中setup暴露出来的数据和方法,但是 vue3 的setup拿不到 vue2 的数据和方法,为 undefined
- 当 vue2 和 vue3 的数据重名时, 以 vue3 为准。
响应式
ref函数(与vue2的ref不是一回事)
定义: 通过ref函数把数据变为引用实现的实例对象RefImpl, 通过getter 和 setter 进行数据劫持,实现响应式。
语法: const xxx = ref(initvalue)
使用:
- 通过
数据名.value的方法可以获取和修改数据值。 - 在模板中通过{{}}使用数据并不需要加value, vue自动给加上了。
- 修改对象中的数据,只有在第一层用
.value,下面直接.就可以获取和修改job.value.type = 'UI设计师'
<script>
import {ref} from 'vue'
export default {
name: 'App',
setup(){
//数据
let name = ref('张三')
let age = ref(18)
let job = ref({
type:'前端工程师',
salary:'30K'
})
//方法
function changeInfo(){
name.value = '李四' // 修改普通数据
job.value.type = 'UI设计师' // 修改对象数据
}
//返回一个对象(常用)
return {
name,
age,
job,
changeInfo
}
}
}
</script>
手写:
基础类型和复杂类型
- 基础类型:响应式依旧是靠
Object.defineObject() 的 set 和 get。 - 复杂类型:第一层仍旧是ref创建的
RefImpl对象,但是属性的响应式是求助了vue3的新函数——reactive实现了proxy代理
reactive函数
定义: 定义一个对象类型的响应式数据
语法:const xxx = reactive({ initvalue }),得到一个proxy对象
- 不用
.value获取值。 - 数组可以直接用索引修改,在vue2中只能用加工过的(vue)JS原生的方法修改数组。
<script>
import {reactive} from 'vue'
export default {
name: 'App',
setup(){
//数据
let person = reactive({
name:'张三',
age:18,
job:{
type:'前端工程师',
salary:'30K',
a:{
b:{
c:666
}
}
},
hobby:['抽烟','喝酒','烫头']
})
//方法
function changeInfo(){
person.name = '李四'
person.age = 48
person.job.type = 'UI设计师'
person.job.salary = '60K'
person.job.a.b.c = 999
person.hobby[0] = '学习'
}
//返回一个对象(常用)
return {
person,
changeInfo
}
}
}
</script>
手写:
reactive对比ref
- 从定义数据角度对比:
- ref用来定义:基本类型数据。
- reactive用来定义:对象(或数组)类型数据。
- 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过
reactive转为代理对象。
- 从原理角度对比:
- ref通过
Object.defineProperty()的get与set来实现响应式(数据劫持)。 - reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
- ref通过
- 从使用角度对比:
- ref定义的数据:操作数据需要
.value,读取数据时模板中直接读取不需要.value。 - reactive定义的数据:操作数据与读取数据:均不需要
.value。
- ref定义的数据:操作数据需要
vue3响应式原理
实现原理:
- 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
- 通过Reflect(反射): 对源对象的属性进行操作。 proxy:对proxy对象的操作都能反射到源对象上,修改时也会触发set
const p = new Proxy(person,{
//有人读取p的某个属性时调用
get(target,propName){
console.log(`有人读取了p身上的${propName}属性`)
return Reflect.get(target,propName)
},
//有人修改p的某个属性、或给p追加某个属性时调用
set(target,propName,value){
console.log(`有人修改了p身上的${propName}属性,我要去更新界面了!`)
Reflect.set(target,propName,value)
},
//有人删除p的某个属性时调用
deleteProperty(target,propName){
console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)
return Reflect.deleteProperty(target,propName)
}
})
Reflect:通过Object.defineProperty去操作属性时,如果是重复的操作,浏览器会抛出错误,而通过Reflect则不会,避免了try catch的问题
//通过Object.defineProperty去操作
//#region
/* try {
Object.defineProperty(obj,'c',{
get(){
return 3
}
})
Object.defineProperty(obj,'c',{
get(){
return 4
}
})
} catch (error) {
console.log(error)
} */
//#endregion
//通过Reflect.defineProperty去操作
//#region
/* const x1 = Reflect.defineProperty(obj,'c',{
get(){
return 3
}
})
console.log(x1)
const x2 = Reflect.defineProperty(obj,'c',{
get(){
return 4
}
})
if(x2){
console.log('某某某操作成功了!')
}else{
console.log('某某某操作失败了!')
} */
//#endregion
// console.log('@@@')
computed计算属性
- 导入 computed 函数
- 向computed 中加入回调。
- 格式: 计算后的值 = computed( () => { return 值 } )
import {reactive,computed} from 'vue'
export default {
name: 'Demo',
setup(){
//数据
let person = reactive({
firstName:'张',
lastName:'三'
})
//计算属性——简写(没有考虑计算属性被修改的情况)
person.fullName = computed(()=>{
return person.firstName + '-' + person.lastName
})
//计算属性——完整写法(考虑读和写)
person.fullName = computed({
get(){
return person.firstName + '-' + person.lastName
},
set(value){
const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
//返回一个对象(常用)
return {
person
}
}
}
watch数据监听(监听ref或reactive或数组)
监听ref定义的基本类型数据
- 从vue中 导入 watch 函数;
- 语法: watch('要监听的数据', (new, old) => {要执行的操作}, {配置项});
- 同时监听多个: 监听的数据写成数组 ['数据1', '数据2'];
//情况一:监视ref所定义的一个响应式数据(不用.value)
watch(sum,(newValue,oldValue)=>{
console.log('sum变了',newValue,oldValue)
},{immediate:true})
//情况二:监视ref所定义的多个响应式数据
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum或msg变了',newValue,oldValue)
},{immediate:true})
监听 reactive 定义的数据
必须是reactive定义的
- 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
- 监视reactive定义的响应式数据中某个属性时:deep配置有效。
监听reactive中的属性时必须用函数返回值的形式,否则监听不到(
只能监听ref对象或reactive对象或是数组)。
/* 情况三:监视reactive定义的响应式数据
若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
若watch监视的是reactive定义的响应式数据,则强制开启了深度监视
对象中的属性对象中的属性变化也能监听到(全部都能监听到)
*/
// 旧value和新value相同,deep全能监听
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{immediate:true,deep:false}) //此处的deep配置不再奏效
//情况四:监视reactive定义的响应式数据中的某个属性(必须写成函数返回值的形式),旧value和新value不同,不配置deep不能监听
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})
//情况五:监视reactive定义的响应式数据中的某些属性
watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})
//只监听reactive中的属性的对象,需要配置deep,否则不触发watch
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
watchEffect函数
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
watchEffect(()=>{
const x1 = sum.value
const x2 = person.age
console.log('watchEffect配置的回调执行了')
})
vue3生命周期
配置项形式及顺序
beforeCreate() {
console.log('---beforeCreate---')
},
created() {
console.log('---created---')
},
beforeMount() {
console.log('---beforeMount---')
},
mounted() {
console.log('---mounted---')
},
beforeUpdate(){
console.log('---beforeUpdate---')
},
updated() {
console.log('---updated---')
},
beforeUnmount() {
console.log('---beforeUnmount---')
},
unmounted() {
console.log('---unmounted---')
},
组合式API(把生命周期放在setup中)应用及顺序
生命周期beforeCreate和created相当于setup,没有专门的API方法。
import {ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue'
setup(){
console.log('---setup---')
//数据
let sum = ref(0)
//通过组合式API的形式去使用生命周期钩子
onBeforeMount(()=>{
console.log('---onBeforeMount---')
})
onMounted(()=>{
console.log('---onMounted---')
})
onBeforeUpdate(()=>{
console.log('---onBeforeUpdate---')
})
onUpdated(()=>{
console.log('---onUpdated---')
})
onBeforeUnmount(()=>{
console.log('---onBeforeUnmount---')
})
onUnmounted(()=>{
console.log('---onUnmounted---')
})
//返回一个对象(常用)
return {sum}
},
注:当配置项生命周期和组合式API同时使用时,先执行的是setup中的生命周期,然后执行配置项中的同一级的声明周期,然后执行setup中的下一生命周期
自定义hook
-
什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。
-
类似于vue2.x中的mixin。
-
自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。 使用:
- 定义一个方法,然后暴露。
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
console.log(event.pageX,event.pageY)
}
//实现鼠标“打点”相关的生命周期钩子
onMounted(()=>{
window.addEventListener('click',savePoint)
})
onBeforeUnmount(()=>{
window.removeEventListener('click',savePoint)
})
return point
}
- 引入想要使用的组件中
<script>
import usePoint from '../hooks/usePoint'
export default {
name:'Test',
setup(){
const point = usePoint()
return {point}
}
}
</script>
toRef 和 toRefs
toRef
定义:toRef是为了使数据在模板中使用更加简介,而不是一直使用对象.属性的形式,如果在setup暴露时直接将对象.属性赋值给key(模板中使用的数据为setup暴露时对象的key),会导致数据不是响应式。而toRef解决了这个问题。
- ref和toRef的区别
- ref根据传入的数据,返回一个新的ref实例对象,地址不指向传入的变量地址,是深拷贝,虽然页面会变,但是原本的对象就不会改变了;
- toRef根据传入的数据,将数据转化为ref形式,但是返回的ref实例对象仍旧指向旧的地址,仍旧响应源对象的get和set; 所以要用toRef转换而不是ref转换。
- 语法: const xxx = toRef(对象名, ‘属性’); 如果属性为深层,则对象.属性到深层转换
return {
person,
name:toRef(person,'name'),
age:toRef(person,'age'),
salary:toRef(person.job.j1,'salary'),
}
toRefs
定义:当对象中的属性过多时,不能使用toRef一个个转换,而需要使用toRefs整体转换,但是只能转换对象的第一层,输出为对象。
return {
person,
...toRefs(person)
}
缺陷: 当后续向对象中新添加属性时,toRefs拆解不出来,因为toRefs只会在setup()执行的时候执行,而setup只执行一次。所以当需要添加新属性时:
- 在原本的对象中开始就注册一个空的属性占位;
- 将整个person对象暴露出去,使用时就用对象.属性的方式。
其他的component API
shallowReactive 和 shallowRef
和reactive的区别: shallowReactive只考虑第一层数据的响应式
和ref的区别: 当传入基本数据时,shallowRef和ref没有区别,当传入对象时,ref会自动调用reactive使数据变为响应式,而shallowRef不会把对象改为响应式
readonly 和 shallowReadonly
readonly: 使数据不允许改变
readonly和 const 的区别: const是变量保护,readonly是属性保护。
shallowReadonly: 只保护对象的第一层数据不被改变,深层可以。
person = readonly(person)
toRaw 和 markRaw
- toRaw:
- 作用:将一个由
reactive生成的响应式对象转为普通对象。 - 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
- 作用:将一个由
- markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式对象吗,但是数据都 被包装成一个Proxy对象。
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类库等。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
customRef
setup() {
//自定义一个ref——名为:myRef
function myRef(value, delay) {
let timer;
return customRef((track, trigger) => {
return {
// 当模板需要数据时就来找get返回
get() {
console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`);
track(); //通知Vue追踪value的变化(提前和get商量一下,让他认为这个value是有用的)
return value;
},
set(newValue) {
console.log(`有人把myRef这个容器中数据改为了:${newValue}`);
clearTimeout(timer);
timer = setTimeout(() => {
// 修改数据会触发,然后把新值付给变量
value = newValue;
trigger(); //通知Vue去重新解析模板
}, delay);
},
};
});
}
// let keyWord = ref('hello') //使用Vue提供的ref
let keyWord = myRef("hello", 500); //使用程序员自定义的ref
return { keyWord };
},
provide 与 inject
-
作用:实现祖与后代组件间通信
-
套路:父组件有一个
provide选项来提供数据,后代组件有一个inject选项来开始使用这些数据 -
具体写法:
-
祖组件中:
setup(){ ...... let car = reactive({name:'奔驰',price:'40万'}) provide('car',car) ...... } -
后代组件中:
setup(props,context){ ...... const car = inject('car') return {car} ...... }
-
响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly创建的只读代理 - isProxy: 检查一个对象是否是由
reactive或者readonly方法创建的代理
新的组件
Fragment
- 在Vue2中: 组件必须有一个根标签
- 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
- 好处: 减少标签层级, 减小内存占用
Teleport
-
什么是Teleport?——
Teleport是一种能够将我们的组件html结构移动到指定位置的技术。<teleport to="移动位置HTML标签"> <div v-if="isShow" class="mask"> <div class="dialog"> <h3>我是一个弹窗</h3> <button @click="isShow = false">关闭弹窗</button> </div> </div> </teleport>
Suspense
-
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
-
这样其实setup() 可以是async,可以返回异步了。
-
使用步骤:
-
异步引入组件
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>其他
API的改变
- Vue 2.x 有许多全局 API 和配置。
-
例如:注册全局组件、注册全局指令等。
//注册全局组件 Vue.component('MyButton', { data: () => ({ count: 0 }), template: '<button @click="count++">Clicked {{ count }} times.</button>' }) //注册全局指令 Vue.directive('focus', { inserted: el => el.focus() }
-
-
Vue3.0中对这些API做出了调整:
-
将全局的API,即:
Vue.xxx调整到应用实例(app)上2.x 全局 API( Vue)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
-
其他改变
其他改变
-
data选项应始终被声明为一个函数。
-
过度类名的更改:
-
Vue2.x写法
.v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; } -
Vue3.x写法
.v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; }
-
-
移除keyCode作为 v-on 的修饰符,同时也不再支持
config.keyCodes -
移除
v-on.native修饰符-
父组件中绑定事件
<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" /> -
子组件中声明自定义事件
<script> export default { emits: ['close'] } </script>
-
-
移除过滤器(filter)
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。
-
......