vue2 和 vue3 不同点汇总
| 1、生命周期 |
| |
| 2、多根节点 |
| |
| 3、Composition Api |
| |
| 4、异步组件 |
| |
| 5、响应式原理 |
| |
| 6、Teleport |
| |
| 7、虚拟 Dom |
| |
| 8、事件缓存 |
| |
| 9、Diff 算法优化 |
| |
| 10、打包优化 |
| |
| 11、TypeScript 支持 |
生命周期
| 1、Vue3 生命周期 整体上变化不大,Vue3 在大部分生命周期钩子名称上 + “on”,功能上是类似的 |
| |
| 2、Vue3 在组合式 API( Composition API )中使用 生命周期钩子 时需要先引入,Vue2 在选项 API(Options API)中可以直接调用生命周期钩子 |
常用的生命周期对比
Vue2 生命周期 |
Vue3 生命周期 |
beforeCreate |
|
created |
|
beforeMount |
onBeforeMount |
mounted |
onMounted |
beforeUpdate |
onBeforeUpdate |
updated |
onUpdated |
beforeDestroy |
onBeforeUnmount |
destroyed |
onUnmounted |
| 说明 |
| |
| Vue3 中 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地去定义 |
| 代码示例 |
| |
| 1、 vue3 |
| |
| <script setup> |
| |
| import { onMounted } from 'vue'; |
| |
| onMounted(() => { |
| |
| }); |
| |
| |
| onMounted(() => { |
| |
| }); |
| |
| </script> |
| |
| 2、vue2 |
| |
| <script> |
| export default { |
| mounted() { |
| |
| }, |
| } |
| </script> |
多根节点
| 1、Vue2 在模板中如果使用多个根节点时会报错 |
| |
| 2、Vue3 支持多个根节点,也就是 fragment |
| |
| 原因 |
| |
| 1、在 vue2 中 |
| |
| 1、因为 vdom 是一个 单根树形结构 描述当前视图结构,patch 方法在遍历的时候从根节点开始遍历,它要求只有一个根节点 |
| |
| 2、组件也是会转换成 vdom,所以也必须满足单根节点要求 |
| |
| 2、在 vue3 中 |
| |
| 1、因为 vue3 引入了 fragment 概念,这是一个抽象的节点,如果发现组件是多根的会自动创建一个 fragment 节点,把多根节点视为自己的 children |
| |
| 2、如果发现这是一个fragment节点,则直接遍历children创建或更新 |
| |
Composition API
| 1、Vue2 是 选项 API(Options API),一个逻辑会散乱在文件不同位置( data、props、computed、watch、生命周期钩子等 ),导致代码的可读性变差。当需要修改某个逻辑时,需要上下来回跳转文件位置。 |
| |
| 2、Vue3 组合式 API(Composition API)则很好地解决了这个问题,可将同一逻辑的内容写到一起,增强了代码的可读性、内聚性,其还提供了较为完美的逻辑复用性方案 |
异步组件( Suspense )
| 1、Vue3 提供 Suspense 组件,允许程序在等待异步组件加载完成前渲染兜底的内容,如 loading,使用户的体验更平滑 |
| |
| 2、使用它,需在模板中声明,并包括两个命名插槽:default 和 fallback |
| |
| Suspense 确保加载完异步内容时显示默认插槽,并将 fallback 插槽用作加载状态 |
| 代码示例 |
| |
| <tempalte> |
| <suspense> |
| <template #default> |
| <List /> |
| </template> |
| <template #fallback> |
| <div> |
| Loading... |
| </div> |
| </template> |
| </suspense> |
| </template> |
| |
| 在 List 组件(有可能是异步组件,也有可能是组件内部处理逻辑或查找操作过多导致加载过慢等)未加载完成前,显示 Loading...(即 fallback 插槽内容),加载完成时显示自身(即 default 插槽内容) |
Teleport
| Vue3 提供 Teleport 组件可将部分 DOM 移动到 Vue app 之外的位置。比如项目中常见的 Dialog 弹窗 |
| |
| <button @click="dialogVisible = true">显示弹窗</button> |
| <teleport to="body"> |
| <div class="dialog" v-if="dialogVisible"> |
| 我是弹窗,我直接移动到了body标签下 |
| </div> |
| </teleport> |
响应式原理
| 1、Vue2 响应式原理基础是 Object.defineProperty |
| |
| 2、Vue3 响应式原理基础是 Proxy |
vue2 响应式基础
| Object.defineProperty 基本用法:直接在一个对象上定义新的属性或修改现有的属性,并返回对象 |
| |
| let obj = {}; |
| let name = 'leo'; |
| Object.defineProperty(obj, 'name', { |
| enumerable: true, |
| configurable: true, |
| |
| |
| get() { |
| return name; |
| }, |
| set(value) { |
| name = value; |
| } |
| }); |
| |
| 提示 【 writable 和 value 】 与 【 getter 和 setter 】 不共存 |
| Vue2 核心源码,略删减 |
| |
| function defineReactive(obj, key, val) { |
| |
| const dep = new Dep() |
| |
| |
| const property = Object.getOwnPropertyDescriptor(obj, key) |
| if (property && property.configurable === false) { return } |
| |
| |
| const getter = property && property.get |
| const setter = property && property.set |
| if((!getter || setter) && arguments.length === 2) { val = obj[key] } |
| |
| |
| let childOb = observe(val) |
| |
| Object.defineProperty(obj, key, { |
| enumerable: true, |
| configurable: true, |
| |
| get: function reactiveGetter() { |
| const value = getter ? getter.call(obj) : val |
| if(Dep.target) { |
| |
| dep.depend() |
| if(childOb) { |
| |
| childOb.dep.depend() |
| |
| if(Array.isArray(value)) { |
| dependArray(value) |
| } |
| } |
| } |
| } |
| return value |
| }) |
| |
| set: function reactiveSetter(newVal) { |
| ... |
| if(setter) { |
| setter.call(obj, newVal) |
| } else { |
| val = newVal |
| } |
| |
| childOb = observe(val) |
| |
| dep.notify() |
| } |
| } |
vue3 响应式基础
| 1、Vue3 为何会抛弃 vue2 的 Object.defineProperty |
| |
| 主要原因:无法监听 对象 或 数组 新增、删除 的元素 |
| |
| Vue2 相应解决方案 |
| |
| 1、针对常用数组原型方法 push、pop、shift、unshift、splice、sort、reverse 进行了 hack 处理;提供 Vue.set 监听对象/数组新增属性 |
| |
| 2、对象的新增/删除响应,还可以 new 个新对象,新增则合并新属性和入旧对象;删除则将删除属性后的对象深拷贝给新对象。 |
| |
| 2、Proxy |
| |
| 1、Proxy 是 ES6 新特性,通过第2个参数 handler 拦截目标对象的行为 |
| |
| 2、相较于 Object.defineProperty 提供语言全范围的响应能力,消除了局限性 |
| |
| 1、支持 对象/数组的 新增、删除 |
| |
| 2、监测 .length 修改 |
| |
| 3、Map、Set、WeakMap、WeakSet 的支持 |
| 基本用法:创建对象的代理,从而实现基本操作的 拦截 和 自定义 操作 |
| |
| let handler = { |
| get(obj, prop) { |
| return prop in obj ? obj[prop] : ''; |
| }, |
| set() { |
| |
| }, |
| ... |
| }; |
| 部分 vue3 的源码 reactive.ts |
| |
| function createReactiveObject(target, isReadOnly, baseHandlers, collectionHandlers, proxyMap) { |
| ... |
| |
| |
| const proxy = new Proxy( |
| target, |
| targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers |
| ) |
| proxyMap.set(target, proxy) |
| return proxy |
| } |
虚拟 Dom
| Vue3 相比于 Vue2,虚拟 DOM 上增加 patchFlag 字段 |
| 我们借助 Vue3 Template Explorer 来看 |
| |
| <div id="app"> |
| <h1>vue3虚拟DOM讲解</h1> |
| <p>今天天气真不错</p> |
| <div>{{name}}</div> |
| </div> |
| |
| 渲染函数如下所示 |
| |
| import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from vue |
| |
| const _withScopeId = n => (_pushScopeId(scope-id),n=n(),_popScopeId(),n) |
| const _hoisted_1 = { id: app } |
| const _hoisted_2 = _withScopeId(() => _createElementVNode(h1, null, vue3虚拟DOM讲解, -1 )) |
| const _hoisted_3 = _withScopeId(() => _createElementVNode(p, null, 今天天气真不错, -1 )) |
| |
| export function render(_ctx, _cache, $props, $setup, $data, $options) { |
| return (_openBlock(), _createElementBlock(div, _hoisted_1, [ |
| _hoisted_2, |
| _hoisted_3, |
| _createElementVNode(div, null, _toDisplayString(_ctx.name), 1 ) |
| ])) |
| } |
| |
| 第 3 个 _createElementVNode 的第4个参数即 patchFlag 字段类型 |
| patchFlags 字段类型列举 |
| |
| export const enum PatchFlags { |
| TEXT = 1, |
| |
| CLASS = 1 << 1, |
| |
| STYLE = 1 << 2, |
| |
| PROPS = 1 << 3, |
| |
| FULL_PROPS = 1 << 4, |
| |
| HYDRATE_EVENTS = 1 << 5, |
| |
| STABLE_FRAGMENT = 1 << 6, |
| |
| KEYED_FRAGMENT = 1 << 7, |
| |
| UNKEYED_FRAGMENT = 1 << 8, |
| |
| NEED_PATCH = 1 << 9, |
| |
| DYNAMIC_SLOTS = 1 << 10, |
| |
| HOISTED = -1, |
| |
| BAIL = -2 |
| } |
事件缓存
| 1、Vue3 的cacheHandler可在第一次渲染后缓存我们的事件 |
| |
| 2、相比于 Vue2 无需每次渲染都传递一个新函数,加一个 click 事件 |
| <div id="app"> |
| <h1>vue3事件缓存讲解</h1> |
| <p>今天天气真不错</p> |
| <div>{{name}}</div> |
| <span onCLick=() => {}><span> |
| </div> |
| |
| import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from vue |
| |
| const _withScopeId = n => (_pushScopeId(scope-id),n=n(),_popScopeId(),n) |
| const _hoisted_1 = { id: app } |
| const _hoisted_2 = _withScopeId(() => _createElementVNode(h1, null, vue3事件缓存讲解, -1 )) |
| const _hoisted_3 = _withScopeId(() => _createElementVNode(p, null, 今天天气真不错, -1 )) |
| const _hoisted_4 = _withScopeId(() => _createElementVNode(span, { onCLick: () => {} }, [ |
| _createElementVNode(span) |
| ], -1 )) |
| |
| export function render(_ctx, _cache, $props, $setup, $data, $options) { |
| return (_openBlock(), _createElementBlock(div, _hoisted_1, [ |
| _hoisted_2, |
| _hoisted_3, |
| _createElementVNode(div, null, _toDisplayString(_ctx.name), 1 ), |
| _hoisted_4 |
| ])) |
| } |
| |
| 观察以上渲染函数,你会发现 click 事件节点为静态节点(HOISTED 为 -1),即不需要每次重新渲染 |
Diff 算法优化
| 1、vue2.x 的 虚拟 DOM 是进行全量比较 |
| |
| 2、vue3 新增了 静态标记 PatchFlag |
| |
| 3、搬运 Vue3 patchChildren 源码 |
| |
| 结合上文与源码 patchFlag 帮助 diff 时区分静态节点,以及不同类型的动态节点,一定程度地减少节点本身及其属性的比对 |
| function patchChildren(n1, n2, container, parentAnchor, parentComponent, parentSuspense, isSVG, optimized) { |
| |
| const c1 = n1 && n1.children |
| const c2 = n2.children |
| const prevShapeFlag = n1 ? n1.shapeFlag : 0 |
| const { patchFlag, shapeFlag } = n2 |
| |
| |
| if(patchFlag > 0) { |
| if(patchFlag && PatchFlags.KEYED_FRAGMENT) { |
| |
| patchKeyedChildren() |
| return |
| } els if(patchFlag && PatchFlags.UNKEYED_FRAGMENT) { |
| |
| patchUnkeyedChildren() |
| return |
| } |
| } |
| |
| |
| if(shapeFlag && ShapeFlags.TEXT_CHILDREN) { |
| if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { |
| unmountChildren(c1 as VNode[], parentComponent, parentSuspense) |
| } |
| if (c2 !== c1) { |
| hostSetElementText(container, c2 as string) |
| } |
| } else { |
| |
| if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { |
| if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { |
| patchKeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense,...) |
| } else { |
| unmountChildren(c1 as VNode[], parentComponent, parentSuspense, true) |
| } |
| } else { |
| |
| if(prevShapeFlag & ShapeFlags.TEXT_CHILDREN) { |
| hostSetElementText(container, '') |
| } |
| if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { |
| mountChildren(c2 as VNodeArrayChildren, container,anchor,parentComponent,...) |
| } |
| } |
| } |
| } |
| patchUnkeyedChildren 源码如下所示 |
| |
| function patchUnkeyedChildren(c1, c2, container, parentAnchor, parentComponent, parentSuspense, isSVG, optimized) { |
| c1 = c1 || EMPTY_ARR |
| c2 = c2 || EMPTY_ARR |
| const oldLength = c1.length |
| const newLength = c2.length |
| const commonLength = Math.min(oldLength, newLength) |
| let i |
| for(i = 0 |
| |
| const nextChild = (c2[i] = optimized ? cloneIfMounted(c2[i] as Vnode)) : normalizeVnode(c2[i]) |
| patch() |
| } |
| if(oldLength > newLength) { |
| |
| unmountedChildren() |
| } else { |
| |
| mountChildren() |
| } |
| } |
打包优化
| 1、Tree-shaking |
| |
| 1、模块打包 webpack、rollup 等中的概念 |
| |
| 2、移除 JavaScript 上下文中未引用的代码 |
| |
| 3、主要依赖于 import 和 export 语句,用来检测代码模块是否被导出、导入,且被 JavaScript 文件使用 |
| |
| 2、以 nextTick 为例子,在 Vue2 中,全局 API 暴露在 Vue 实例上,即使未使用,也无法通过 tree-shaking 进行消除 |
| |
| import Vue from 'vue'; |
| |
| Vue.nextTick(() => { |
| |
| }); |
| |
| 3、Vue3 中针对全局和内部的 API 进行了重构,并考虑到 tree-shaking 的支持。因此,全局 API现在只能作为 ES模块 构建的命名导出进行访问 |
| |
| import { nextTick } from 'vue'; |
| |
| nextTick(() => { |
| |
| }); |
| 1、通过这一更改,只要模块绑定器支持 tree-shaking,则 Vue 应用程序中未使用的 api 将从最终的捆绑包中消除,获得最佳文件大小 |
| |
| 2、受此更改影响的全局API如下所示。 |
| |
| Vue.nextTick |
| |
| Vue.observable (用 Vue.reactive 替换) |
| |
| Vue.version |
| |
| Vue.compile (仅全构建) |
| |
| Vue.set (仅兼容构建) |
| |
| Vue.delete (仅兼容构建) |
| |
| 3、内部 API 也有诸如 transition、v-model 等标签或者指令被命名导出 |
| |
| 只有在程序真正使用才会被捆绑打包 |
| |
| 4、Vue3 将所有运行功能打包也只有约 22.5kb,比 Vue2 轻量很多。 |
TypeScript 支持
| Vue3 由 TypeScript 重写,相对于 Vue2 有更好的 TypeScript 支持 |
| |
| 1、Vue2 Options API 中 option 是个简单对象,而 TypeScript 是一种类型系统,面向对象的语法,不是特别匹配。 |
| |
| 2、Vue2 需要 vue-class-component 强化 vue 原生组件,也需要 vue-property-decorator 增加更多结合Vue特性的装饰器,写法比较繁琐 |
其他变化
| 1、移除了 Event Bus |
| |
| 1、Vue3 从实例中移除了 o n 、 on、 on、off 和 $once 方法 |
| |
| 2、如果希望继续使用全局事件总线的话,就需要通过第三方库 |
| |
| Vue3 官方有推荐一些库,例如 mitt 或 tiny-emitter |
| |
| 2、vue3 取消了 vue2 中的过滤器 |
| |
| 过滤器打破了大括号内的表达式 “只是 JavaScript” 的假设 |
| |
| 建议使用 |
| |
| 1、在双括号表达式中调用方法 实现 |
| |
| 2、计算属性来替换 过滤器 |
Options API 与 Composition API
| Vue 组件可以用两种不同的 API 风格编写 |
| |
| 1、Options API 【 选项 Api 】 |
| |
| 2、Composition API 【 组合式 Api 】 |
Options API 【 选项 Api 】
| 1、使用 Options API,我们使用选项对象定义组件的逻辑 |
| |
| 1、例如 data、methods 和 mounted |
| |
| 2、由选项定义的属性在 this 内部函数中公开,指向组件实例 |
| |
| 2、代码示例 |
| |
| <template> |
| <button @click="increment">count is: {{ count }}</button> |
| </template> |
| |
| <script> |
| export default { |
| data() { |
| return { |
| count: 0 |
| } |
| }, |
| methods: { |
| increment() { |
| this.count++; |
| } |
| }, |
| mounted() { |
| console.log(`The initial count is ${this.count}.`); |
| } |
| } |
| </script> |
Composition API 【 组合式 Api 】
| 使用 Composition API,我们使用导入的 API 函数定义组件的逻辑。 |
| |
| 在 SFC 中 通常使用 Composition API |
| |
| <template> |
| <button @click="increment">Count is: {{ count }}</button> |
| </template> |
| |
| <script setup> |
| import { ref, onMounted } from 'vue'; |
| |
| const count = ref(0); |
| |
| function increment() { |
| count.value++; |
| } |
| |
| onMounted(() => { |
| console.log(`The initial count is ${count.value}.`); |
| }) |
| </script> |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)