vue3 取经

前言

vue 官网提供的:vue2 与 vue3 的对比

vue 2 常见的缺陷:

  • vue 2 的响应式并不是真正意义上的代理,而是基于 Object.defineProperty() 实现的。这个 API 并不是代理,而是对某个属性进行拦截,所以有很多缺陷,比如:删除数据就无法监听,需要 $delete 等 API 辅助才能监听到。
  • vue 2 是使用 Flow.js 来做类型校验,但现在 Flow.js 已经停止维护了,整个社区都在全面拥抱 TypeScript 来构建基础库。
  • Vue 2 内部所有的模块都是揉在一起的,这样做会导致不好扩展的问题。

Vue 3 的新特性概览:

  • 响应式系统
  • Composition API 组合语法
  • 新的组件:
    • Fragment
    • Teleport
    • Suspense
  • Vite
  • 自定义渲染器
  • Vue 的全部模块使用 TypeScript 重构。
  • RFC 机制(了解)

一、响应式系统

Vue 2 的响应式机制是基于 Object.defineProperty() 实现的,而 vue 3 采用了 Proxy,这两者的区别一言以蔽之——defineProperty 是拦截具体某个属性,Proxy 才是真正的“代理”

在 Vue 3 中则使用了 Proxy 来创建响应式对象,仅将 getter / setter 用于 ref。

1、vue2 的基于 Object.defineProperty() 的响应式

关于 Object.defineProperty() 详见这里

语法:

Object.defineProperty(data, 'count', {
  // 读取
  get () {},
  // 修改
  set () {}
})

原理:

  • 对象:对对象的已有属性进行劫持(拦截/监听)。
  • 数组:vue2 内部通过重写 “更新数组的一系列方法” 来实现对数组元素的劫持。

问题:为什么我数据都改变了,但是页面没有重新渲染呢?(🌟🌟🌟)

  • 对象:在对象内新添加属性 或 删除已有的属性,页面不会自动更新。
  • 数组:直接通过下标替换元素或者修改数组的 length 值,页面不会自动更新。

vue2 给出的解决方案:为了解决 defineProperty 带来的问题,vue2 中增加 $set 方法。

2、vue3 的基于 ES6 的 Proxy 的响应式

ES6 的 Proxy 和 Reflect

proxy 代理需要用到两个对象:

语法:

new Proxy(data, {
  // 读取
  get (target, prop) {
	return Reflect.get(target, prop)
  },
  // 添加、修改
  set (target, prop, val) {
	return Reflect.set(target, prop, val)
  },
  // 删除
  deleteProperty (target, prop) {
	return Reflect.deleteProperty(target, prop)
  }
  // ...
})

(1)、Proxy 对象

Proxy 对象是一个构造函数,可以通过 new 关键字来创建一个代理对象的实例。

语法:

const p = new Proxy(target, handler)

参数:

  • target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
  • handler:一个通常以函数作为属性的对象(换言之,Proxy 对象的 handler 对象的成员是一些函数),各属性中的函数分别定义了在执行各种操作时代理 p 的行为。它包含有 Proxy 的各个捕获器(trap)。所有的捕捉器是可选的。如果没有定义某个捕捉器,那么就会保留源对象的默认行为。

方法:

  • Proxy 的静态方法:Proxy.revocable() ——创建一个可撤销的 Proxy 对象。
  • handler 的方法(见下表)
方法描述
handler.getPrototypeOf()Object.getPrototypeOf 方法的捕捉器
handler.setPrototypeOf()Object.setPrototypeOf 方法的捕捉器
handler.isExtensible()Object.isExtensible 方法的捕捉器
handler.preventExtensions()Object.preventExtensions 方法的捕捉器
handler.getOwnPropertyDescriptor()Object.getOwnPropertyDescriptor 方法的捕捉器
handler.defineProperty()Object.defineProperty 方法的捕捉器
handler.has()in 操作符的捕捉器
handler.get()属性读取操作的捕捉器
handler.set()属性设置操作的捕捉器
handler.deleteProperty()delete 操作符的捕捉器
handler.ownKeys()Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器
handler.apply()函数调用操作的捕捉器
handler.construct()new 操作符的捕捉器

(2)、Reflect 对象

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 Proxy handler 的方法相同。

Reflect 不是一个构造函数,所以不能通过 new 运算符调用,也不能将 Reflect 对象作为一个函数来调用。Reflect 的所有属性和方法都是静态的。调用静态属性和方法必须使用本身类名来调用。

Reflect 对象的静态方法:

方法描述
Reflect.apply(target, thisArgument, argumentsList)对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和 Function.prototype.apply() 功能类似
Reflect.construct(target, argumentsList[, newTarget])对构造函数进行 new 操作,相当于执行:new target(...args)
Reflect.defineProperty(target, propertyKey, attributes)Object.defineProperty() 类似。如果设置成功就会返回 true
Reflect.deleteProperty(target, propertyKey)作为函数的 delete 操作符,相当于执行:delete target[name]
Reflect.get(target, propertyKey[, receiver])获取对象身上某个属性的值,类似于:target[name]
Reflect.getOwnPropertyDescriptor(target, propertyKey)类似于 Object.getOwnPropertyDescriptor()。如果对象中存在该属性,则返回对应的属性描述符,否则返回 undefined
Reflect.getPrototypeOf(target)类似于 Object.getPrototypeOf()
Reflect.has(target, propertyKey)判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同
Reflect.isExtensible(target)类似于 Object.isExtensible()
Reflect.ownKeys(target)返回一个包含所有自身属性(不包含继承属性)的数组。(类似于 Object.keys(), 但不会受 enumerable 影响)
Reflect.preventExtensions(target)类似于 Object.preventExtensions()。返回一个 Boolean
Reflect.set(target, propertyKey, value[, receiver])将值分配给属性的函数。返回一个 Boolean,如果更新成功,则返回 true
Reflect.setPrototypeOf(target, prototype)设置对象原型的函数。返回一个 Boolean, 如果更新成功,则返回 true

【注意】其中的一些方法与 Object 相同,尽管二者之间存在某些细微上的差别

(3)、proxy 代理的原理与优势

1⃣️、原理

  • 通过 proxy 拦截对 data 任意属性的任意操作,包括:属性的增删,以及属性值的读写。
  • 通过 Reflect 动态对被代理的对象的相应的属性进行特定的操作。

2⃣️、优势

可以监听更多的数据格式,比如: SetMap。这是 Vue 2 做不到的。


二、vue 实例

1、创建一个 vue 实例

在 vue3 中,通过 createApp 函数可以创建一个 vue 实例。

const app = Vue.createApp({
  /* 选项 */
})

2、将 Vue 实例挂载到根组件上

假设你的根组件是 <div id="app"></div>,你应该这样:

const RootComponent = {}
// 创建一个 vue 实例
const app = Vue.createApp(RootComponent)
// 将 vue 实例挂载到根组件上
const vm = app.mount('#app')

这等价于 vue2 中的这种写法:

const vue = new Vue({
  el: '#app',
  render: h => h(App)
})

【注意】在 vue2 中将 h 作为 createElement 函数的别名。

3、vue 实例的 生命周期 及其在 setup 中的映射

  • 在 vue3 中可以使用 vue3 规定的 Options-based API 生命周期钩子函数,但更推荐使用 Composition API,使用两者对应的 API 时二选一即可。
  • Composition API 里的生命周期回调 总是比 Options-based API 的生命周期钩子函数 更快一步执行。
vue2、vue3 实例的生命周期及其在 setup 中的对照表
vue2 的生命周期(Options-based API) vue3 的生命周期(Options-based API) vue3 的生命周期(Composition API)
beforeCreate 在实例初始化之后,进行数据侦听和事件/侦听器的配置之前同步调用。 beforeCreate 同 vue2。 setup() 描述
created 在实例创建完成后被立即同步调用。在这一步中,实例已完成对选项的处理,意味着以下内容已被配置完毕:数据侦听、计算属性、方法、事件/侦听器的回调函数。然而,挂载阶段还没开始,且 $el property 目前尚不可用。 created 同 vue2。
beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用。该钩子在服务器端渲染期间不被调用。 beforeMount 同 vue2。 onBeforeMount 描述
mounted 实例被挂载后调用,这时 el 被新创建的 vm.$el 替换了。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm.$el 也在文档内。注意 mounted 不会保证所有的子组件也都被挂载完成。如果你希望等到整个视图都渲染完毕再执行某些操作,可以在 mounted 内部使用 vm.$nextTick。该钩子在服务器端渲染期间不被调用。 mounted 同 vue2。 onMounted 描述
beforeUpdate 在数据发生改变后,DOM 被更新之前被调用。这里适合在现有 DOM 将要被更新之前访问它,比如移除手动添加的事件监听器。该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务器端进行。 beforeUpdate 同 vue2。 onBeforeUpdate 描述
updated 在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或侦听器取而代之。注意,updated 不会保证所有的子组件也都被重新渲染完毕。如果你希望等待整个视图都渲染完毕,可以在 updated 内部使用 vm.$nextTick。该钩子在服务器端渲染期间不被调用。 updated 同 vue2。 onUpdated 描述
activated 被 keep-alive 缓存的组件“激活”时调用。该钩子在服务器端渲染期间不被调用。 activated 同 vue2。 - -
deactivated 被 keep-alive 缓存的组件“失活”时调用。该钩子在服务器端渲染期间不被调用。 deactivated 同 vue2。 - -
beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。该钩子在服务器端渲染期间不被调用。 beforeUnmount 1. 在 vue3 的项目中 beforeDestroy 改名为 beforeUnmount,所以不能用 beforeDestroy 了。

2. 在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。该钩子在服务器端渲染期间不被调用。
onBeforeUnmount 描述
destroyed 实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。该钩子在服务器端渲染期间不被调用。 unmounted 1. 在 vue3 的项目中 destroyed 改名为 unmounted,所以不能用 destroyed 了。

2. 卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。该钩子在服务器端渲染期间不被调用。
onUnmounted 描述
errorCaptured 在捕获一个来自后代组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。 errorCaptured 同 vue2。 onErrorCaptured 描述
- - renderTracked 跟踪虚拟 DOM 重新渲染时调用。钩子接收 debugger event 作为参数。此事件告诉你哪个操作跟踪了组件以及该操作的目标对象和键。 onRenderTracked Tracked 读作:[trækt],跟踪。
- - renderTriggered 当虚拟 DOM 重新渲染被触发时调用。和 renderTracked 类似,接收 debugger event 作为参数。此事件告诉你是什么操作触发了重新渲染,以及该操作的目标对象和键。 onRenderTriggered Triggered 读作:[ˈtrɪɡəd],触发。

vue2 的生命周期图(左/上)和 vue3 的生命周期图(右/下)——摘自 Vue 官网:
在这里插入图片描述在这里插入图片描述

我们可以在一个组件渲染时:

  • 使用 onRenderTracked 生命周期钩子来调试查看哪些依赖正在被使用。
  • 使用 onRenderTriggered 来确定哪个依赖正在触发更新。

这些钩子都会收到一个调试事件,其中包含了触发相关事件的依赖的信息。推荐在回调中放置一个 debugger 语句,使你可以在开发者工具中交互式地查看依赖。例如:

<script setup>
import { onRenderTracked, onRenderTriggered } from 'vue'

onRenderTracked((event) => {
  debugger
})

onRenderTriggered((event) => {
  debugger
})
</script>

详情请参阅:这里


三、Composition API 组合语法

vue3 响应性基础 API
vue3 组合式 API
<script setup> 的官方使用说明文档

1、Vue 的 Options API 与 Composition API 对比

(1)、Options API 的写法也有几个很严重的问题

  • 由于所有数据都挂载在 this 之上,因而 Options API 的写法对 TypeScript 的类型推导很不友好,并且这样也不好做 Tree-shaking(摇树) 清理代码。
  • 新增功能基本都得修改 data、method 等配置,并且代码上 300 行之后,会经常上下反复横跳,开发很痛苦。
  • 代码不好复用,Vue 2 的组件很难抽离通用逻辑,只能使用 mixin,还会带来命名冲突的问题。

【拓展】Tree Shaking 指的就是:当我引入一个模块的时候,我不引入这个模块的所有代码,我只引入我需要的代码,这就需要借助 webpack 里面自带的 Tree Shaking 这个功能来帮我们实现。在 webpack 项目中,有一个入口文件,相当于一棵树的主干,入口文件有很多依赖的模块,相当于树枝。实际情况中,虽然依赖了某个模块,但其实只使用其中的某些功能。通过 Tree-Shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的。

(2)、使用 Composition API 带来的好处

  • 所有 API 都是 import 引入的。用到的功能都 import 进来,对 Tree-shaking 很友好,没有用到的功能,打包的时候会被清理掉 ,减小包的大小。
  • Composition API 能够将同一个逻辑关注点的相关代码收集在一起,每个功能模块都在一起维护。不再上下反复横跳,维护更轻松。
  • 代码方便复用,可以把一个功能所有的 methods、data 封装在一个独立的函数里,复用代码非常容易。

2、使用 Composition API

Composition API 组合语法有以下 3 种使用方式:

  • 直接 setup 函数中使用 Composition API 组合语法。
  • 通过 defineComponent 函数来使用 Composition API 组合语法。
  • 在 <script setup> 标签中使用 Composition API 组合语法。

(1)、在 setup 里使用 vue3 的生命周期

在 setup 里可以通过以下方法的回掉函数来实现 vue3 中与之对应的生命周期:

  • onBeforeMount
  • onMounted
  • onBeforeUpdate
  • onUpdated
  • onBeforeUnmount
  • onUnmounted
  • onRenderTracked
  • onRenderTriggered
  • onErrorCaptured

例如:

<script>
import {
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted,
  onRenderTracked,
  onRenderTriggered,
} from "vue";

export default {
  setup() {
    // setup 里面存着两个生命周期创建前和创建后:beforeCreate 和 created
    onBeforeMount(() => {
      console.log("onBeforeMount");
    });
    onMounted(() => {
      console.log("onMounted");
    });
    onBeforeUpdate(() => {
      console.log("onBeforeUpdate");
    });
    onUpdated(() => {
      console.log("onUpdated");
    });
    onBeforeUnmount(() => {
      // 在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。
      console.log("onBeforeUnmount");
    });
    onUnmounted(() => {
      // 卸载组件实例后调用,调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。
      console.log("onUnmounted");
    });
    /** 新增的两个生命周期函数 **/
    // 1、每次渲染后重新收集响应式依赖
    onRenderTracked(({ key, target, type }) => {
      // 跟踪虚拟DOM重新渲染时调用,钩子接收 debugger event 作为参数,此事件告诉你哪个操作跟踪了组件以及该操作的目标对象和键。
      // type: set/get 操作。
      // key: 追踪的键。
      // target: 重新渲染后的键。
      console.log("onRenderTracked");
    });
    // 2、每次触发页面重新渲染时自动执行
    onRenderTriggered(({ key, target, type }) => {
      // 当虚拟DOM重新渲染被触发时调用,和 renderTracked 类似,接收 debugger event 作为参数。
      // 此事件告诉你是什么操作触发了重新渲染,以及该操作的目标对象和键。
      console.log("onRenderTriggered");
    });
    return {};
  },
};
</script>

(2)、setup 函数、defineComponent 函数 和 script 标签上的 setup

vue3 setup 函数、defineComponent 函数 和 script 标签上的 setup

(3)、setup 里的计算属性(computed)和侦听器(watch)

vue3 中 setup 里的计算属性(computed)和侦听器(watch)

(4)、基于 setup 的组件通信

vue3 基于 setup 的组件通信

(5)、Vue3 的 API 方法

Vue3 的 API 方法


四、h() 函数——创建 Vnodes

vue 用 h() 函数创建 Vnodes


五、新的组件

官方给出的说明:

  • Fragment: Vue 3 组件不再要求有一个唯一的根节点,清除了很多无用的占位 div
  • Teleport: 允许组件渲染在别的元素内,主要开发弹窗组件的时候特别有用
  • Suspense: 异步组件,更方便开发有异步请求的组件

vue3 的内置组件汇总


六、Vite 工程化工具

Webpack 等工程化工具的原理,是根据你的 import 依赖逻辑,把所有路由的依赖打包后,形成一个依赖图,然后调用对应的处理工具,把整个项目打包后,放在内存里再启动调试。由于要预打包,所以复杂项目的开发,启动调试环境需要大量的时间。Vite 就是为了解决这个时间资源的消耗问题出现的。

现代浏览器已经默认支持了 ES6 的 import 语法,Vite 就是基于此来实现的——在调试环境下,我们不需要全部预打包,只是把你首页依赖的文件,依次通过网络请求去获取,达到秒级的热更新。

Vite 的工作原理:一开始就可以准备联调,然后根据首页的依赖模块,再去按需加载。把加载丢给了浏览器,这样启动调试所需要的资源会大大减少。

vite 前端构建工具
vite.config 配置文件
给 vite 创建的 vue3 项目配置 ESLint


七、自定义渲染器

Vue 2 内部所有的模块都是揉在一起的,这样做会导致不好扩展的问题。Vue 3 使用 monorepo 管理方式进行拆包,从而实现了:把响应式、编译和运行时全部独立了。渲染的逻辑也拆成了平台无关渲染逻辑和浏览器渲染 API 两部分(用 Vue 开发跨端应用时会用到自定义渲染器)。
目前,Google、Facebook、 Babel、Vue3 都使用了 monorepo 方案来管理他们的代码。

单一代码库(monorepos) 和 多代码库(multirepos):

  • monorepo 是一种将多个项目代码存储在一个仓库里的软件开发策略。
  • MultiRepo 是另一种将每个项目对应一个单独的仓库来分散管理。
    请添加图片描述

【注意】多代码库不是微服务(microservices)的同义词,两者之间并没有耦合关系。事实上,我们稍后将讨论将单一代码库和微服务结合起来的例子。只要仔细设置用于部署的CI/CD流水线,单一代码库就可以托管任意数量的微服务。

多项目版本管理:monorepo 策略
Vue3.0 中的 monorepo 管理模式


八、采用 TypeScript 重构

vue 3 全部模块使用 TypeScript 重构,这样做的好处是:

  • 类型系统带来了更方便的提示。
  • 类型系统代码能够更健壮。

关于 TS 的学习请戳这里:TypeScript 语法


九、RFC 机制(了解)

RFC(Request For Comments)意即“请求评论”,用来向网友征求意见的。

vue 团队就是通过这种方式,先通过 vue 社区向广大网友征求意见,然后综合评估取舍后研发出来的 vue 3。

如果你想了解 Vue 团队开发的工作方式,或者你想对 Vue 源码作出贡献,那么你需要深入了解 vue3 的 RFC 的机制 )。


十、vue3 踩坑记

vue3 踩坑记
vue3踩坑记录
vue3使用中踩过的坑
vue3踩坑史
深入解读vue3 watch(踩坑记录)




【参考】
vue3 官网
W3Cschool上的vue3教程
让你30分钟快速掌握vue3教程
Vue3.0 在编译时针对虚拟 DOM 的性能优化

【推荐】
迎接Vue3.0
vue3源码

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值