vue3学习笔记

341 阅读6分钟

vue3介绍

2020年9月发布正式版

对比vue2

  • vue3 支持大多数的 vue2 特性

  • 设计了一套强大的组合API 代替了 vue2 的 options API, 复用性更强

  • 更好的支持 TS

  • 最主要: vue3中使用了ProxyReflect 代替了 vue2 的 Object.defineProtory 实现数据的响应式

  • 重写了虚拟DOM,速度更快了

  • 设计了一个新的脚手架工具 vite

  • 生命钩子:vue3里 beforeDestroy/destroyed 改名成 beforeUnmount/unmounted

  • vue3 里没有 beforeCreated/created 对应的组合API,改成setup

  • 新的组件: Fragment、Teleport、Suspense

  • 模板可以没有根标签

  • defineCompoment 函数:定义一个组件,接收一个配置对象options

// vue2 App为一个对象
// App: export default {}
new Vue({
  render: h => h(App),
}).$mount('#app');


// vue3 App为一个组件 defineComponent(options)
// App: export default defineComponent({})
createApp(App).mount('#app')

响应式

vue2的响应式

  • 核心:

    • 对象: 通过defineProperty对对象的已有属性值的读取和修改进行劫持(监视/拦截)

    • 数组: 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持(push、pop、unshift、shift、sort、reverse、splice)

  • 问题

    • 对象直接新添加的属性或删除已有属性, 界面不会自动更新

    • 直接通过下标替换元素或更新length, 界面不会自动更新

Object.defineProperty(data, 'num', {
    get () {}, 
    set () {}
})

vue3的响应式

  • 核心:

    • 通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等

    • 通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作

const proxy = new Proxy(data, {
	// 拦截读取属性值
    get (target, prop) {
    	return Reflect.get(target, prop)
    },
    // 拦截设置属性值或添加新属性
    set (target, prop, value) {
    	return Reflect.set(target, prop, value)
    },
    // 拦截删除属性
    deleteProperty (target, prop) {
    	return Reflect.deleteProperty(target, prop)
    }
})

proxy.name = 'tom'  

vue3 composition API

setup
  • 执行时机:

    • 在beforeCreate之前执行(一次), 此时组件对象还没有创建

    • this是undefined, 不能通过this来访问data/computed/methods / props

  • 返回值:

    • 返回一个对象,对象里包括属性及方法

    • 返回对象中的属性会与data函数返回对象的属性合并成为组件对象的属性

    • 返回对象中的方法会与methods中的方法合并成为组件对象的方法

    • 如果有重名, setup优先

    • setup不能是一个async函数: 因为返回值不再是return的对象, 而是promise

  • 参数:

    • setup(props, context) / setup(props, {attrs, slots, emit})

    • props: 包含props配置声明且传入了的所有属性的对象

    • attrs: 包含没有在props配置中声明的属性的对象, 相当于 this.$attrs

    • slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots

    • emit: 用来分发自定义事件的函数, 相当于 this.$emit

ref 和 reactive
  • ref函数: 定义一个基本类型的响应式数据,返回一个响应式对象,js中通过value属性操作其值。

    • const ref = ref(initValue)

    • js中操作: ref.value

  • reactive函数:定义多个数据的响应式(深层)

    • const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象

    • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的

对比 ref 和 reactive

  • ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式)

  • 如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象

  • ref内部: 通过给value属性添加getter/setter来实现对数据的劫持

  • ref的数据操作: 在js中要.value, 在模板中不需要(内部解析模板时会自动添加.value)

  • reactive内部: 通过使用Proxy来实现对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据

toRef

  • 为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的

  • 区别ref: ref拷贝了一份新的数据值单独操作, 更新时相互不影响

customRef

  • 创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制

  • 接收一个工厂函数,该函数接收 track 和 trigger 两个参数,并返回一个带 get 和 set 的对象

function useDebounce (value, delay = 200) {
  let timer
  return customRef((track, trigger) => {
    return {
      get() {
        track() // 通知vue追踪数据
        return value
      },
      set(newVal) {
        clearTimeout(timer)
        timer = setTimeout(() => {
          value = newVal
          trigger() // 通知vue更新界面
        }, delay)
      },
    }
  })
}

shallowReactive 与 shallowRef

  • shallowReactive : 只处理了对象内最外层属性的响应式(也就是浅响应式)

  • shallowRef: 只处理了value的响应式, 不进行对象的reactive处理

  • 什么时候用浅响应式呢?

    • 一般情况下使用ref和reactive即可

    • 如果有一个对象数据, 结构比较深, 但变化时只是外层属性变化 ===> - shallowReactive

    • 如果有一个对象数据, 后面会产生新的对象来替换 ===> shallowRef

readonly 与 shallowReadonly

  • readonly:

    • 深度只读数据

    • 获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。

    • 只读代理是深层的:访问的任何嵌套 property 也是只读的。

  • shallowReadonly

    • 浅只读数据

    • 创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换

toRaw 与 markRaw

  • toRaw

    • 返回由 reactive 或 readonly 方法转换成响应式代理的普通对象。

    • 这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。

  • markRaw

    • 标记一个对象,使其永远不会转换为代理。返回对象本身

计算属性computed与监视watch

  • computed函数:

    • 与computed配置功能一致

    • 只有getter

    • 有getter和setter

  • watch函数

    • 与watch配置功能一致

    • 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调

    • 默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次

    • 通过配置deep为true, 来指定深度监视

    • watch多个数据,使用数组来指定;如果是ref对象, 直接指定;如果是reactive对象中的属性, 必须通过函数来指定

  • watchEffect函数

    • 监视所有回调中使用的数据
import { defineComponent, ref, reactive, computed, watch, watchEffect } from 'vue'
export default defineComponent({
  setup () {
    const obj = reactive({firstName: '', lastName: ''})

    // 传入一个回调,只有getter
    const comp1 = computed(() => {
      return obj.firstName + obj.lastName
    })

    // 传入一个对象,getter和setter
    const comp2 = computed({
      set (value: string) {
        obj.firstName = value
      },
      get () {
        return obj.firstName + obj.lastName
      },
    })


    let wat1 = ref('')

    // watch 一个数据
    watch(obj, (value) =>{
      wat1.value = value.firstName
    }, {
      immediate: true, // 是否初始化立即执行一次, 默认是false
      deep: true  // 是否是深度监视, 默认是false
    })

    // watch 多个数据
    watch([wat1, () => obj.firstName, () => obj.lastName], (values) => {
      console.log(values)
    })


    watchEffect(() => {
      wat1.value = obj.firstName + obj.lastName
    }) 


    return {
      comp1,
      comp2,

      wat1,
    }
  }
})

生命周期钩子

对比vue2

  • vue3里没有 beforeCreated/created 这两个钩子,改成 setup

  • vue3里 beforeDestroy/destroyed 改名成 beforeUnmount/unmounted

  • 选项 API 生命周期选项和组合式 API 之间的映射:

    • beforeCreated -> setup
    • created -> setup
    • beforeMount -> onBeforeMount
    • mounted -> onMounted
    • beforeUpdate -> onBeforeUpdate
    • updated -> onUpdated
    • beforeUnmount -> onBeforeUnmount
    • unmounted -> onUnmounted
    • errorCaptured -> onErrorCaptured
    • renderTracked -> onRenderTracked
    • renderTriggered -> onRenderTriggered

自定义hook函数

使用Vue3的组合API封装的可复用的功能函数,类似vue2的mixin

// hooks/useHooks.ts
import { ref, onMounted, onBeforeUnmount } from 'vue'
export default function () {
  const x = ref(0)
  const y = ref(0)
  const func = (e: MouseEvent) => {
    x.value = e.pageX
    y.value = e.pageY
  }
  onMounted(() => {
    document.addEventListener('click', func)
  })

  onBeforeUnmount(() => {
    document.removeEventListener('click', func)
  })
  return {
    x, y
  }
}


// vue文件
import useHooks from '../hooks/useHook'
export default defineComponent({
  setup () {
    const { x, y } = useHooks()
    return { x, y }
  }
})

toRefs函数

把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref

import { toRefs } from 'vue'

setup () {
  const obj = reactive({
    a: 122,
    b: 333
  })

  return {
    ...toRefs(obj) // 模板中直接用 a、b, 不用obj.a、obj.b
  }
}

响应式数据的判断

  • isRef: 检查一个值是否为一个 ref 对象

  • isReactive: 检查一个对象是否是由 reactive 创建的响应式代理

  • isReadonly: 检查一个对象是否是由 readonly 创建的只读代理

  • isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

组件间通信

组件通信和 vue2 区别不大,都是那么几种,此处不再一一说明,只举例 provide/inject

provide 和 inject
  • provide和inject提供依赖注入,功能类似 2.x 的provide/inject

  • 实现跨层级组件(祖孙)间通信

// 父组件
import { defineComponent, provide } from 'vue'
import Gradeson from './gradeson.vue'

export default defineComponent({
  components: {
    Gradeson
  },
  setup () {
    let color = ref('red')
    provide('color', color)
    return {
      color
    }
  }
})


// 子孙组件
import { defineComponent, inject } from 'vue'

export default defineComponent({
  setup () {
    let color = inject('color')
    return {color}
  }
})

组件

Fragment 组件:片段
  • 在Vue2中: 组件必须有一个根标签

  • 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中

  • 好处: 减少标签层级, 减小内存占用

Teleport 组件:瞬移
  • Teleport 提供了一种干净的方法, 让组件的html在父组件界面外的特定标签(很可能是body)下插入显示
<teleport to="#some-id">xxx</teleport>
<teleport to=".some-class">xxx</teleport>
<teleport to="body">xxx</teleport>
Suspense 组件
  • 它们允许我们的应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验
<template>
  <Suspense>
    <template v-slot:default>
      <AsyncComp/>
    </template>

    <template v-slot:fallback>
      <h1>loading...</h1>
    </template>
  </Suspense>
</template>

<script lang="ts">
 
// 异步组件 + Suspense组件
// 动态组件引入:
// vue2: const AsyncComp = () => import('./AsyncComp.vue')
// vue3: 使用 defineAsyncComponent

import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => import('./AsyncComp.vue'))
export default {
  setup() {
    return {
     
    }
  },

  components: {
    AsyncComp
  }
}
</script>

性能提升

  • 打包大小减少41%

  • 初次渲染快55%,更新渲染快133%

  • 内存减少54%

  • 使用Proxy代替Object.defineProperty实现数据响应式

  • 重写虚拟DOM的实现和Tree-Shaking