Vue3-vs-Vue2

news/2025/3/9 8:32:55/

Vue3 性能提升

与 Vue2.x 相比, mount 50% 提升, 内存占用小 120%

Vue3 框架体积减少

  • 核心代码 + Composition API: 13.5kb, 最小 11.75kb
  • 所有 Runtime: 22.5kb (Vue2 是 32kb)

Vue3 新特性

  • TS 重写 Diff 算法, 使用 Proxy 性能更优, 框架体积更小
  • 新的 Compiler, 通过注释标记提升框架性能
  • Composition API, 模块化功能代码, 摒弃 this
  • 更好的按需加载 (得益于 Tree Shaking), Webpack 打包时能更好的按需打包
  • 新增: Fragment、Teleport、Suspense
  • Vite 开发工具

为什么摒弃 this ?

this 的指向不明确, 不清楚代码的调用在哪里

采用函数式编程, 虽然比较偏激, 但是带来的好处就是类型推导变得非常简单, 而且容易理解, 类似于 python

响应式原理

Proxy 原理

  • proxy 代替 Object.defineProperty() 实现数据响应
  • Proxy 是 ES6 的语法, 有浏览器兼容性问题

Proxy 介绍

MDN: Proxy 对象用于定义基本操作的自定义行为 (如属性查找、赋值、枚举、函数调用等)

  • 拦截功能
  • 提供对象访问
  • 可以重写属性或者构造函数

Proxy 为什么 比 Object.defineProperty 更好

我们创建一个对象且声明属性, 使用 Object.defineProperty 去监听属性的变化, 也就是为每个属性赋予 get、set 方法, 因为对象可能是多层对象, 所以它需要深度去遍历每个属性, 这个时候遍历已经完成了。 但是新增,删除属性, 这个时候不会对新增、删除属性赋予 get、set 方法。所以, Vue2 才无法对对象属性进行新增、删除。所以开发的时候使用, Vue2 都是先声明属性, 再对数据进行操作。

Proxy 是不是对原始个对象进行监听, 而是去监听原始对象的代理对象, 代理对象只要变化, 就会触发对应 get、set 函数。

ref vs reactive 用法与区别

如何定义一个响应式变量或数组、对象?

使用 ref、reactive

核心:

在 vue3 中调用 ref 函数,时会 js 会先 new 一个 class,这个 class 监听了 value 属性的 get 和 set 方法实现了在 get 中收集依赖,在 set 中触发依赖的双向绑定过程

如果需要对传入参数深层监听的话,就需要调用 reactive 方法

javascript">// 自定义ref (伪代码不能直接运行,仅用于学习)
function ref(target) {const result = { // 这里在源码中体现为一个类 RefImpl_value: reactive(target), // target 传给 reactive 方法做响应式处理,如果是对象的话就变成响应式get value () {return this._value},set value (val) {this._value = valconsole.log('set value 数据已更新, 去更新界面')}}return result
}// 测试
const ref = ref(9);
ref.value = 6;const ref = ref({a: 4});
ref.value.a = 6;

区别:

  • reactive参数一般接受 对象或数组,是深层次的响应式。ref参数一般接收 简单数据类型,若ref接收对象为参数,本质上会转变为reactive方法

  • 在JS中访问ref的值需要手动添加.value,访问reactive不需要

  • reactive 直接赋值对象或者数组会丢失响应式 res = data,而 ref 就不会

  • 响应式的底层原理都是 Proxy

javascript">// reactive 数组对象赋值方法// 法一
const state = reactive({arr: []
})
state.arr = [1, 2, 3]// 法二
const state = ref([])
state.value = [1, 2, 3]// 法三
const arr = reactive([])
arr.push(...[1, 2, 3])
// ref、reactive 的基本使用定义
const total = ref('Lin')
const state = reactive({lists: [{id: 1,text: 'zhang san'},{id: 2,text: 'li si'}]})// 使用
total.value = 'Pin'
state.lists.id = 3

对于数组的操作

vue2 中不能直接操作数组, 如 this.arr[0] = 233, 这样做的话, 数组改变了, 但是视图不会渲染

但是 vue3 中就可以直接操作数组, 且视图会渲染, 这是 vue3 的一个新的特点

readonly 使用方法及场景

const original = reactive({ count: 0 })const copy = readonly(original)watchEffect(() => {// 用来做响应性追踪console.log(copy.count)
})// 更改源属性会触发其依赖的侦听器
original.count++// 更改该只读副本将会失败,并会得到一个警告
copy.count++ // warning!

场景: 比如, 子组件使用父组件的数据, 但是不想子组件去修改这个数据, 就可以使用这个属性了

toRefs 的使用

主要作用是从组合逻辑函数中返回一个响应式的对象

reactive 转换响应式副本, readonly 创建只读对象

isProxy/isReactive/isReadonly & toRaw/toRef/toRefs

toRaw: 将响应式对象转换为普通对象

toRefs: 将非响应式对象转换为响应式的映射

  • 其他 API 代补充

Compiler 原理篇

优化 - 1

  • 静态 Node 不再作更新处理 (hoistStatic -> SSR优化)
  • 静态绑定的 class, id 不再作更新处理
  • 结合打包标记 PatchFlag, 进行更新分析 (动态绑定)

优化 - 2

  • 事件监听器 Cache 缓存处理 (cacheHandles)
  • hoistStatic 自动针对多静态节点进行优化, 输出字符串

Diff 算法比较

Vue2 Diff 算法

  • 新旧节点对比, 先从前面对比, 再从后面对比, 抽离出一样的部分
  • 从第一元素开始对比, 通过移位, 新增来实现更新

Vue3 Diff 算法

  • patchChildren 根据是否存在 key 进行真正的 diff (静态标记 - 标记会动态改变的节点)
  • 复用真实的 dom 节点, 避免不必要的性能开销
  • 乱序时, 先找到最长递增子序列作为参考, 然后再移动, 减少比较次数, 提高了性能 (重点)

核心就是子节点之间的对比, 主要分为两种情况

  • 子节点无 key (只作长度对比)
  • 子节点有 key

无 key 时, 我们希望尽可能复用老节点

  • 比较新老 children 的 length 获取最小值
  • 对于公共部分, 进行从新 patch 工作
  • 如果长于旧, 则新增; 如果短于旧, 则删除

watch 与 watchEffect

watch 在 vue2、vue3 中区别

// vue3 中的写法
setup () {watch(count, (count, prevCount) => {/* ... */})
}// vue2 中的写法
watch: {counter (newval:number, oldval:number) {console.log(newval, oldval)}}

vue2.x中Watch 和 vue3.x中Watch使用和区别

watchEffect 的使用

watchEffect(() => {const total = counter.value * 2console.log('total is:' + total)
}, {flush: 'pre', // pre | post | sync (触发的时机设置)onTrack: (e) => {console.log('onTrack -> e', e)}
})

watch vs watchEffect

  • 都可以监听响应式对象的变化, 从而执行回调
  • watchEffect 会默认执行一次, 而 watch 不会, 必须事件触发
  • watchEffect 只接收函数作为回调函数

计算属性 (computed) 的使用

// vue3 
let sum = computed(()=>{return num1.value + num2.value
})let sum = computed({get:()=>{return num1.value *10},set:(value)=>{return num1.value = value/10}
})// vue2
computed: {total () {console.log('🚀 ~ file: HomeView.vue:39 ~ total ~ counter:', this.counter)return this.counter * 2}total:{get(){ // num1值改变时触发return this.num1 * 10},set(value){ // mul值被改变时触发this.num1 = value /10}}
}// 传递参数
const sltEle = computed(() => {return function (index) {console.log('index', index) }
}) 

[Vue2、Vue3 中 computed 的使用与不同 ](https://www.cnblogs.com/web-learn/p/15601335.html#:~:text=在vue2中,computed 写法: computed%3A { sum () {,return this.num1%2B this.num2 } } 在vue3 如果使用选项式API也可以这样写,主要看下组合式API的使用。)

如何在 ts 中配置 this 指向问题

// tsconfig.json
"lib": ["esnext","dom","dom.iterable","scripthost"
],
"noImplicitThis": false // 配置这个属性为 false 就不会再检查 this 指向问题

implicitly has an ‘any‘ type 解决方法

// tsconfig.json
"compilerOptions": {"target": "esnext","module": "esnext","noImplicitAny": false, // 配置这个属性就可以忽略 any 报错了"strict": true,"jsx": "preserve","moduleResolution": "node","skipLibCheck": true,"esModuleInterop": true,"allowSyntheticDefaultImports": true,"forceConsistentCasingInFileNames": true,"useDefineForClassFields": true,"sourceMap": true,"baseUrl": ".","types": ["webpack-env"],
}

Vue3 生命周期钩子函数

调试时使用的钩子函数

onRenderTracked() 当组件渲染过程中追踪到响应式依赖时调用 (调用一次)

onRenderTriggered() 当响应式依赖的变更触发了组件渲染时调用 (改变就调用)

Vue3 选项式 API 名称的改动

destroy 改为 unmounted

beforeDestroy 改为 beforeUnmounted

setup() 优先于 beforeCreate 执行

选项式 API 的生命周期, 及钩子函数 (钩子函数就是在 生命周期前面 加上 on 而已)

Suspense 简介 & 封装ErrorCapture组件

Suspense 的基本使用

<template><div><Suspense><template #default> <!-- 异步加载的组件 --><User /></template><template #fallback> <!-- 加载过程中的提示 -->loadding ...</template></Suspense></div>
</template>

封装 ErrorCapture 组件

<!-- ErrorCapture 组件 -->
<template><slot v-if="error" name="error" :err="error"></slot><Suspense v-else><template #default><slot name="default"></slot></template><template #fallback><slot name="fallback"></slot></template></Suspense>
</template><script lang="ts">
import { defineComponent, onErrorCaptured, ref } from 'vue'export default defineComponent({setup () {const error = ref('')onErrorCaptured((err) => {error.value = err as unknown as stringreturn true})return {error}}
})
</script><style scoped></style>
<!--使用 ErrorCapture 组件-->
<template><SuspenseWithError><template #error="{err}">{{ err }}</template><template #default><User /> <!-- 这个是自己定义的组件 --></template><template #fallback>loadding ...</template></SuspenseWithError>
</template><script lang="ts">
import { defineComponent } from 'vue'
import User from '@/components/User.vue'
import SuspenseWithError from './components/SuspenseWithError.vue'export default defineComponent({name: 'App',components: {User,SuspenseWithError}
});
</script><style></style>

teleport 组件介绍

使用场景

有时候我们需要放置元素到根节点以外的位置 ( 例如 全屏遮罩 )

teleport 组件

  • 控制部分 DOM 脱离根节点
  • 可以使用本地化逻辑控制组件
  • 适用于 fixed 或者 绝对定位的组件

Teleport 的 to 属性可以指定的对象格式

  • id -> <teleport to = “#id” >
  • claass -> <teleport to = “.className” >
  • data -> <teleport to = "[data-meta]" >
  • 动态 -> <teleport :to = "props" >

Teleport 的基本能使用

<!-- 使用 -->
<template><teleport to='#end-of-body' > <!-- 通过 id 标签 -->hello teleport to end of body</teleport><div>This should be at the top. </div>
</template>
<!-- 指定标签 -->
<body><div id="app"></div><div id="end-of-body"></div> <!-- 设置 id --></body>

Teleport 相关特性

  • :disabled = true (显示在 app 中)

  • :disabled = false (显示在指定元素内)

  • disable 不会隐藏元素, 要隐藏元素可以使用 v-if

<template><teleport to='#end-of-body' :disabled="!toggle" v-if="toggle">hello from teleport</teleport><div>toggle value: {{ toggle }}</div><div>This should be at the top.</div><button type="button" @click="toggleHandle">toggleHandle</button>
</template>

总结

  • teleport 传送 DOM 到 to 属性指定的绑定位置
  • teleport 是保持状态的 (比如视频播放状态会被保持, 不会中断播放), 使用 disbled 属性关闭传送, disble = true 关闭
  • teleport 中可以传送多组 DOM, 按照先后顺序 append

Vue2 与 Vue3 的总结

为什么用 Proxy API 代替 defineProperty API

Vue2 defineProperty API 对一个对象进行增加与删除属性都没有响应式, 用 API (pop、push) 操作数组也没有响应式, 数组用下标修改就可以 arr[0] = 100

Vue3 Proxy API 解决了 defineProperty 的不足, 可以对对象增加、删除属性, 可以使用数组操作 API

API 上的区别 或者说 语法上的区别

setup 中没有 this

具有类型推导, 也就是在编辑器中鼠标放在变量或者方法上能显示其类型, 也就是说 vue3 能自动推导出类型

Vue3 API 分类

选项式 API (Options API)

这类 API 主要是原来 vue2 里的一些 API, 如 watch, computed 写在 export default 里面的

组合式 API (Composition API)

该类 API 是指 vue3 可以抽离出来的 API, 如 setup 里面的 watch、watchEffect、computed

性能上的优化

diff 算法优化

静态提升

事件监听缓存

默认情况下 “绑定事件” 会被视为动态绑定,所以每次都会追踪他的变化。但是函数还是同一个函数所以没必要追踪,直接缓存起来复用就好了,这就是所说的事件监听缓存

SSR 优化

重点回答(Vue2 与 Vue3 的差别)?

1. 双向绑定原理不同

Vue2 使用 object.defineProperty,它后添加的属性是监听不到的,会导致数据更新,但是视图不更新这个问题。当然可以使用 $set 来解决这个问题

Vue3 使用 Proxy 代理,是可以劫持到后添加的属性的


http://www.ppmy.cn/news/1577771.html

相关文章

每日一题———面试

一、http和https的区别是什么&#xff1f; 区别主要有以下四点&#xff1a; 1.HTTP是超文本传输协议&#xff0c;信息是文明传输&#xff0c;存在安全风险的问题。HTTPS则解决HTTP不安全的缺陷&#xff0c;在TCP和HTTP网络层之间加入SSL/TLS安全协议&#xff0c;使得报文能够加…

【数据仓库与数据挖掘基础】决策分析

一、数据仓库中的决策分析 1. 定义 决策分析在数据仓库中是指利用存储在数据仓库中的数据&#xff0c;通过分析和报告生成支持决策的信息。数据仓库整合了来自不同来源的数据&#xff0c;经过清洗和转换&#xff0c;提供了一个统一的视图&#xff0c;帮助管理层和决策者做出明…

vLLM + Open-WebUI 本地私有化部署 DeepSeek-R1-Distill-Qwen-32B 方案

一、vLLM 部署 DeepSeek-R1-Distill-Qwen-32B DeepSeek-R1-Distill 系列模型是 DeepSeek-R1 的蒸馏模型&#xff0c;官方提供了从 1.5B - 70B 不同尺寸大小的模型。特别适合在计算资源有限的环境中部署。 DeepSeek-R1 各个版本的蒸馏模型评估结果如下&#xff1a; 其中 DeepS…

使用浏览器插件GitZip一次性下载多个Github文件

在Edge插件商店中下载GitZip 在github中选择想要下载的文件 然后你会发现右下角一个黑箭头→&#xff0c;点击黑箭头&#xff0c;会出现提示&#xff1a; 你就按照它的提示来&#xff0c;点击GitZip的图标&#xff0c;接着会出现&#xff1a; 点击Private&#xff0c;然后按照…

Android 仿 DeepSeek 思考效果:逐字显示 AI 生成内容,支持加粗、颜色,复制内容

最近特别火的非AI莫属了&#xff0c;对我们高级搬运工太友好了&#xff0c;不管是网页端还是APP端&#xff0c;输入你要问的问题&#xff0c;都会逐字给你显示出来&#xff0c;有的还会加粗&#xff0c;这种效果看着不错&#xff0c;今天就带大家一起来实现。有啥更好的建议请在…

Array and string offset access syntax with curly braces is deprecated

警告信息 “Array and string offset access syntax with curly braces is deprecated” 是 PHP 中的一个弃用警告&#xff08;Deprecation Notice&#xff09;&#xff0c;表明在 PHP 中使用花括号 {} 来访问数组或字符串的偏移量已经被标记为过时。 背景 在 PHP 的早期版本…

现代密码学体系架构设计原则与实践:基于Python的实现与GPU加速GUI演示

目录 现代密码学体系架构设计原则与实践:基于Python的实现与GPU加速GUI演示一、前言二、现代密码学体系架构设计原则1. 安全性原则2. 模块化设计3. 最小权限原则4. 加密算法的选择5. 硬件加速与GPU应用6. 可扩展性与可维护性三、主要加密算法解析1. 对称加密算法:AES2. 非对称…

Python包结构与 `__init__.py` 详解

1. 什么是 __init__.py&#xff1f; __init__.py 是Python包的标识文件&#xff0c;它告诉Python解释器这个目录应该被视为一个包&#xff08;Package&#xff09;。这个文件可以为空&#xff0c;也可以包含初始化代码。 1.1 基本作用 包的标识 将普通目录转换为Python包允许…