Vue3

217 阅读6分钟

一、Vue3

1. vue2和vue3的区别

1.1 数据双向绑定原理的变化

  • vue3使用了ES6中的ProxyAPI对数据代理, 监测的是整个对象, 不再是某个属性
  • vue2使用的是ES5的一个APIobject.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中引入refreactive
    • 简单数据类型用ref()方法声明; 复杂数据类型用reactive()方法声明
    • setup语法糖里返回的响应式数据, 在template模板中可以直接获取

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属性才能读取或修改,模板中使用不需要.value
    • reactive定义的数据,操作数据不需要加.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
    },
  },
})