一、Vue3
1. vue2和vue3的区别
1.1 数据双向绑定原理的变化
- vue3使用了ES6中的
ProxyAPI对数据代理, 监测的是整个对象, 不再是某个属性 - vue2使用的是ES5的一个API
object.definProperty()对数据进行劫持,结合发布订阅模式实现
vue3使用Proxy数据代理的优势:
definProperty只能对某个属性进行监听,且无法监听数组元素的变化. 如果要监听的对象层次较深,性能不好, 会产生很多额外内存;
Proxy直接代理整个对象而非对象属性, 这样直接减少了性能消耗,同时Proxy还可以监听数组的变化
1.2 根节点的不同
- vue2在组件中只能有一个根节点
- vue3支持碎片化, 可以有多个根节点
1.3 API模式的不同
- vue2使用选项式API
data里面声明变量,methods里面声明方法 - vue3使用组合式API 引入
setup语法糖, 同一批逻辑,声明的变量和方法都在同一位置
组合式API的优势: 代码更易于维护;方便阅读;可读性大大增加;
1.4 声明数据方式的不同
-
vue2: 所有的变量都放入
data属性中 -
vue3: 使用
setup语法糖, 也可以声明setup()方法, 此操作的目的是,在组件初始化构造的时候触发; -
vue3通过以下步骤声明响应式数据
- 从vue3中引入
ref或reactive - 简单数据类型用
ref()方法声明; 复杂数据类型用reactive()方法声明 - 在
setup语法糖里返回的响应式数据, 在template模板中可以直接获取
- 从vue3中引入
1.5 生命周期函数的不同
vue3的生命周期函数:
- setup() :开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method
- onBeforeMount() : 组件挂载到节点上之前执行的函数。
- onMounted() : 组件挂载完成后执行的函数。
- onBeforeUpdate(): 组件更新之前执行的函数。
- onUpdated(): 组件更新完成之后执行的函数。
- onBeforeUnmount(): 组件卸载之前执行的函数。
- onUnmounted(): 组件卸载完成后执行的函数
vue3 性能更高, 体积更小, 更利于复用, 代码维护更方便
2. vue2和vue3Diff算法的区别
在数据变更触发页面重新渲染, 此时会生成虚拟DOM 并进行patch过程 , 这一过程在vue3中的优化有以下几个方面:
- 编译阶段的优化
- 事件缓存: 将事件缓存(例如:
@click) , 可以理解为变成静态的 - 静态提升: 第一次创建静态节点时保存, 后续直接复用
- 添加静态标记: 给节点添加静态标记, 用于优化Diff过程
- 事件缓存: 将事件缓存(例如:
由于编译阶段的优化, 除了可以更快的生成虚拟DOM以外, 还使得Diff时可以跳过[永远不会变化的节点]
- Diff优化
- vue2是全量Diff, vue3是静态标记 + 非全量Diff
- 使用最长递增子序列优化了对比流程
3. setup语法糖
script setup是在单文件组件中使用组合式API编译时的语法糖.
相比于普通的setup函数写法更加简洁
<script setup>
console.log('hello script setup')
</script>
4. 函数
4.1 ref
ref是一个vue3内置的函数, 用于创建响应式的数据引用, 可以用来包装普通的javascript数据, 使这个数据具有响应式能力
- 使用方式:
// 导入`ref`函数
import {ref} from 'vue'
// 创建响应式的数据引用
// 这个值可以是基本类型, 也可以是对象类型
const myRef = ref(0)
// 访问创建的值
console.log(myRef.value)
// 修改数据
myRef.value = 2
以这种方式修改引用的值会触发重新渲染组件,并且新的值也会具有响应式的能力。
使用 ref 创建的引用会返回一个包含 value 属性的响应式对象。
可以在模板中直接使用这个引用或者通过 .value 访问引用的值。
4.2 reactive
和ref函数类似, reactive定义一个对象类型的响应式数据。 注意: 基本数据类型还是用ref函数
- 使用方法
import {reactive} from 'vue'
const obj = {
name:'xx',
a:{
b:{c:666}
},
arr:['吃饭','睡觉']
}
const person = reactive(obj)
面试常问 reactive 对比 ref
- 定义数据角度
ref用来定义:基本数据类型;ref也可以定义对象,内部会通过reactive转为代理对象reactive用来定义:对象/数组等复杂数据类型
- 原理角度
ref内部通过vue2的数据响应式原理object.defineProperty()的getter和seteer来实现响应式reactive通过proxy来实现响应式(vue3的数据响应式)。
- 使用方式角度
ref定义的数据,操作数据必须加上.value属性才能读取或修改,模板中使用不需要.valuereactive定义的数据,操作数据不需要加.value属性
4.3 toRefs
- 使用场景:如果对一个响应数据,进行解构 或者展开, 那么原来的对象会丢失响应式
- 原因:vue3 源码是对整个对象进行监听劫持
- 解决:使用
toRefs函数 toRefs函数的作用:- 对一个响应式对象内部所有的属性都做了响应式处理
reactive/ref的响应式是赋值给了对象,如果这个对象解构或者展开,会让数据丢失响应式- 使用
toRefs可以保证对象展开的每个属性都是响应式的
4.4 computed
- 该函数用来创造计算属性, 和
vue2一样, 返回的是一个ref对象. - 里面可以传方法或者对象, 对象中包含
set()、get()方法
4.5 watch
watch函数用来侦听特定的数据源, 并在回调函数中执行副作用
- 具体用法:
// 监听单个ref
const money = ref(100)
watch(money, (value, oldValue) => {
console.log(value)
})
// 监听多个ref
const money = ref(100)
const count = ref(0)
watch([money, count], (value) => {
console.log(value)
})
// 监听ref复杂数据
const user = ref({ name: 'zs', age: 18, })
watch( user, (value) => {
console.log('user变化了', value) },
{
// 深度监听,,,当ref的值是一个复杂数据类型,需要深度监听
deep: true,
immediate: true
})
// 监听对象的某个属性的变化
const user = ref({ name: 'zs', age: 18, })
watch(() => user.value.name, (value) => {
console.log(value)
})
5. v-model
先看v-model在vue2的使用
<Child v-model="title" />
这种写法实际是下面写发的简化版
<Child :value="title" @input="title = $event" />
v-model在vue2就是传递一个属性value, 然后接收一个input事件
有一个应用情景,需要对
prop进行双向绑定,以弹框组件为例,父组件可以定义弹框是否为visible,并通过prop的方式传递给子组件,子组件也可以控制visible是否隐藏,并将visible的值传递给父组件。可以通过以下方式将visible的值传递给父组件:
this.$emit('update:visible',false)
- 然后在父组件中监听这个事件进行数据更新:
<Dialog :visible="isVisible" @update:visible="isVisible=$event"/>
// 也以上代码可以使用`v-bind.sync`来简化:
<Dialog :visible.sync="isVisible"/>
vue3的语法
在Vue3中,自定义组件上使用v-model,相当于传递一个modelValue属性,同时触发update:modelValue事件
<Child v-model="isVisible">
// 相当于
<Child :modelValue="isVisible" @update:modelValue="isVisible=$event">
还可以绑定多个v-model
<Child v-model:visible="isVisible" v-model:content="content"/>
//以上代码相当于:
<Child :visible="isVisible" :content="content" @update:visible="isVisible" @update:content="content"/>
6. pinia
- pinia相比vuex4,具备完善的 类型推荐 => 对 TS 支持很友好
- 对于vue3的 兼容性 更好
核心概念:
- state: 状态
- actions: 修改状态(包括同步和异步,pinia中没有mutations)
- getters: 计算属性
vuex只能有一个根级别的状态, pinia 直接就可以定义多个根级别状态
例如 vue3后台管理权限管理模块中,按钮权限的实现就是通过pinia中的permission
- permission.ts
import { defineStore } from 'pinia'
import { publicRouters } from '@/router'
import { getRolePermission } from '@/api/role'
import type { RouteRecordRaw } from 'vue-router'
import { arrToTree } from '@/utils/util'
import Layout from '@/layout/index.vue'
import { menuHideDic, menuCacheDic } from '@/dictionary/menu'
const modules = import.meta.glob('../views/**/**.vue')
export const usePermissionStore = defineStore('permission', {
state: () => ({
routes: new Array<any>(),
permissions: new Array<any>(),
}),
actions: {
async getAccessRoutes() {
let result = (await getRolePermission()).data
let { menus, permissions } = result
menus.map((item: any) => {
if (!item.parentId) {
item.component = Layout
} else {
item.component = modules[`../views${item.component}.vue`]
}
item.meta = {
title: item.title,
icon: item.icon,
sort: item.sort,
cache: item.cache === menuCacheDic.trueValue,
affix: item.menuType === '2' && item.affix === menuHideDic.trueValue,
hidden: item.hidden === menuHideDic.trueValue,
alwaysShow:
item.menuType === '1' && item.alwaysShow === menuHideDic.trueValue,
}
})
// 递归处理后台返回的路由数据
const routes: RouteRecordRaw[] = arrToTree({
list: menus,
id: '_id',
pid: 'parentId',
children: 'children',
})
this.routes = publicRouters.concat(routes)
this.permissions = permissions
return routes
},
},
})