Vue2和Vue3的区别

262 阅读15分钟

1 组件优化

1 Fragment(多根组件)

  • Vue2 限制:组件模板必须单根节点,导致冗余包裹元素。
  • Vue3 改进:支持多根节点,减少 DOM 层级。

2 Teleport(传送门)

  • 功能:将组件渲染到 DOM或者是vue实例 任意位置(如全局弹窗)。
  • 场景:解决样式隔离或 DOM 嵌套限制问题。

需要注意的是,虽然 DOM 插头被传送到另一个地方,但它的父组件仍然是当前组件,这一点必须牢记,否则会导致样式、交互等问题。

Teleport 组件不仅支持具体的 id/选择器,还可以为to属性绑定一个 Vue 组件实例,比如:

<template>
  <teleport :to="dialogRef">
    <div>这里是瞬移到Dialog组件里的组件</div>
  </teleport>
  <Dialog ref="dialogRef"></Dialog>
</template>

总之,Teleport 组件是 Vue3 中新增的一个非常有用的组件,可以方便地实现一些弹出框、提示框等组件的功能,提高了开发效率。

3 Suspense(异步组件)

  • 功能:优雅处理异步组件加载状态,defalut插槽存放加载后内容,fallback插槽存放加载中的过渡内容。
  • 场景:数据请求、动态导入组件时的加载中/错误状态。

2 TS支持

ts 和 js 的区别是什么? 问题: 解释 TypeScript 相比 JavaScript 增加了哪些特性?

答案:

  • 类型系统(静态类型检查)。
  • 接口(Interface)与类型别名(Type)。
  • 泛型支持。
  • 装饰器(Decorators)。
  • 对象结构约束与可选属性。
  • 更好的 IDE 支持(自动补全、错误提示)。

拓展点1: any与unknown区别

TypeScript中anyunknown的核心区别在于类型安全性:any完全绕过类型检查,允许任意操作且无编译错误,而unknown要求显式类型检查或断言后才能操作,强制开发者确保类型安全。‌‌

‌any‌:

  • ‌适用场景‌:快速迁移JavaScript代码或临时绕过复杂类型问题。‌‌
  • ‌风险‌:破坏类型系统,增加运行时错误和维护难度。‌‌

‌unknown‌:

  • ‌适用场景‌:处理动态数据(如API响应)时保持类型安全,强制开发者显式处理类型问题。‌‌
  • ‌优势‌:比any更安全,推荐替代any。‌‌
let value: unknown = 'hello';
if (typeof value === 'string') {
value.toUpperCase(); // 安全操作
}

拓展点2:‌ TypeScript 中的泛型(Generics)是一种允许定义可复用、可适应不同类型代码的特性**‌。

泛型通过使用类型参数(如T、K、V等)来定义函数、类或接口,这些参数在编译时会被替换为具体的类型。

拓展点3:< >合在一起是单书名号或尖括号

拓展点4:泛型(Generics)主要作用包括提高代码的复用性和类型安全性。可分为

  • ‌函数泛型‌:允许编写可以接受不同类型参数并返回相同类型的函数。例如,function identity<T>(arg: T): T { return arg; } 这个函数可以接受任何类型的参数并返回相同类型的值‌。

  • ‌类泛型‌:允许创建可以处理多种类型数据的类。例如,class Box<T> { value: T; constructor(value: T) { this.value = value; } } 这个类可以存储任何类型的值‌。

  • ‌接口泛型‌:允许定义具有多种类型成员的通用接口。例如,interface Pair<T, U> { first: T; second: U; } 这个接口可以定义具有两个不同类型成员的通用接口‌。

泛型的应用场景和优势

  • ‌提高代码复用性‌:通过泛型,可以编写出适用于多种数据类型的代码,减少重复代码的编写和维护工作‌
  • ‌增强类型安全性‌:泛型确保了在使用时传入正确的类型参数,避免了使用 any 类型带来的类型安全问题‌

拓展点5:ts中 Fn是一个类型别名,表示一个函数类型。如type Fn = (a: number, b: number) => number;

type Sum = (a: number, b: number) => number;

const add: Sum = (x, y) => x + y;
console.log(add(2, 3)); // 输出: 5

3 保留原来的Options API,新增Composition API

提升了逻辑复用和代码组织;函数式写法也更适合ts。

其中vue3新增了reactive函数和ref函数

拓展点1: ‌js中复杂数据类型 = 引用数据类型

  • reactive函数定义复杂数据类型的响应式数据,在js和模板中可直接使用
  • ref函数定义所有数据类型的响应式数据,在js中要.value来调用,模板中直接使用

遵循:尽量使用 ref 函数支持所有场景,确定字段的对象使用 reactive 可以省去.value

  • toRef:用于将某个对象的某个属性转换为响应式引用
  • toRefs:用于将某个对象的所有属性转换为响应式引用
import { reactive, toRef } from 'vue';

const state = reactive({ count: 0 });
const countRef = toRef(state, 'count');
console.log(countRef.value); // 0
import { reactive, toRefs } from 'vue';

const state = reactive({ count: 0, name: 'Vue' });
const { count, name } = toRefs(state);
console.log(count.value); // 0
console.log(name.value); // Vue

拓展点2:

  • computed: 计算属性,返回的是一个基于响应式依赖的缓存值。只有当相关的响应式依赖(如ref或reactive对象中的属性)发生变化时,才会重新计算其值,否则直接返回。
  • watch:可监听单个或多个源,有明确监听目标的优先使用watch进行精确监听,写法:watch(source, callback, options?),可获取旧值
  • watchEffect:立即执行函数,自动追踪所有访问的响应式属性,无旧值处理逻辑

watch:

// 监听单个ref
const count = ref(0)
watch(count, (newVal, oldVal) => {
  console.log(`Count changed from ${oldVal} to ${newVal}`)
})

// 监听getter函数
watch(
  () => state.user.age,
  (age) => console.log('Age updated:', age)
)

// 监听多个源
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  // 处理变化逻辑
})

image.png

watchEffect:

const stop = watchEffect((onCleanup) => {
  console.log('Count:', count.value)
  
  onCleanup(() => {
    console.log('Cleanup previous effect')
  })
})

// 停止监听
stop()

computed和watch详细对比: blog.csdn.net/2401_853928…

拓展点3: Composition API 2种写法区别

1. 传统写法(不使用 <script setup>

需要手动通过 setup() 函数返回暴露的内容。

<script>
import { ref, reactive, onMounted } from 'vue';

export default {
  name: 'MyComponent',
  setup() {
    // 定义响应式数据
    const count = ref(0);
    const state = reactive({ name: 'Vue 3' });

    // 定义方法
    const increment = () => {
      count.value++;
    };

    // 生命周期钩子
    onMounted(() => {
      console.log('组件已挂载');
    });

    // 必须返回模板中需要使用的数据和方法
    return {
      count,
      state,
      increment
    };
  }
}
</script>

特点

  • 必须使用 export default 定义组件。
  • 需要在 setup() 函数中手动返回暴露的内容。
  • 适合需要明确控制暴露内容的场景。
  1. <script setup> 语法(推荐) 自动暴露顶层变量和方法,无需手动返回
<script setup>
import { ref, reactive, onMounted } from 'vue'; //可以使用 unplugin-auto-import 插件来省略导入语句。

// 定义响应式数据
const count = ref(0);
const state = reactive({ name: 'Vue 3' });

// 定义方法
const increment = () => {
  count.value++;
};

// 生命周期钩子
onMounted(() => {
  console.log('组件已挂载');
});
</script>

特点

  • 不需要 export default,整个脚本视为 setup() 函数。
  • 所有顶层变量、函数自动暴露给模板,无需手动返回。
  • 代码更简洁,适合大多数场景。

详解:www.cnblogs.com/smile-fanyi…

拓展4:export default和export

export default是ES6模块系统中的默认导出语法,用于指定模块的默认输出,一个模块只能有一个export default导出。‌它允许在导入时使用任意变量名接收默认导出的值,常用于Vue组件定义或简化模块导入过程。

‌核心特性与语法‌

‌唯一性‌:每个模块仅能使用一次export default,通常用于导出模块的主要功能或对象。‌‌‌‌

‌导入灵活性‌:导入时可自定义接收变量名(无需花括号),例如import MyComponent from './module.js'。‌‌‌‌

‌常见用途‌: Vue单文件组件中导出组件选项对象。‌‌

简化工具库或类的导入,如import axios from 'axios'。‌‌

‌与export的区别‌

特性export defaultexport
导出次数仅一次多次
导入语法无需花括号,变量名可自定义需花括号,按名称匹配或使用as别名
典型场景模块主功能、Vue组件工具函数、常量等需按需导入的成员

‌注意事项‌

  • ‌值类型‌:export default导出的是值的拷贝(原始类型)或引用(对象类型),取决于具体数据类型。‌‌
  • ‌错误示例‌:避免在代码块(如函数内)使用export default,需保持模块顶层作用域。‌‌

4 生命周期变化

阶段组合式 API 钩子选项式 API 钩子调用时机说明
创建阶段setup()beforeCreate/created组合式 API 中 setup() 替代了 Vue 2 的这两个钩子,统一处理初始化逻辑
挂载阶段onBeforeMountbeforeMount模板编译完成但未挂载到 DOM
onMountedmountedDOM 挂载完成,可安全操作 DOM 元素
更新阶段onBeforeUpdatebeforeUpdate数据变化后,虚拟 DOM 重新渲染前触发
onUpdatedupdated虚拟 DOM 重新渲染并应用补丁后触发
销毁阶段onBeforeUnmountbeforeUnmount实例销毁前(Vue 2 的 beforeDestroy 更名,语义更清晰)
onUnmountedunmounted实例销毁后(Vue 2 的 destroyed 更名)

关键说明:

  1. ‌命名变化:Vue 3 钩子名称更语义化(如 unmount 替代 destroy),组合式 API 需添加 on 前缀。
  2. setup() 整合:替代 Vue 2 的 beforeCreate 和 created,集中处理响应式数据、方法等初始化逻辑。
  3. ‌选项式 API 兼容:Vue 3 仍支持选项式写法(如 mounted),但推荐使用组合式 API。

setup函数是组合式API的入口函数,默认导出配置选项,setup函数声明,返回模板需要数据与函数。

  • setup 函数是 Vue3 特有的选项,作为组合式API的起点
  • 从组件生命周期看,它在 beforeCreate 之前执行
  • 函数中 this 不是组件实例,是 undefined
  • 如果数据或者函数在模板中使用,需要在 setup 返回
  • 今后在vue3的项目中几乎用不到 this , 所有的东西通过函数获取。

5 底层响应式原理不同

Vue2使用的是Object.defineProperty()来劫持各个属性的setter/getter,在数据发生变化的时候通知订阅者更新视图。

缺点:

  • 无法检测到对象的属性添加和删除
  • 无法检测到数组的内部变化,因此Vue2通过重写数组方法来实现数组的响应式
  • 需要遍历整个对象,如果对象嵌套过深,需要递归遍历,性能会下降

Vue3使用的是Proxy来劫持整个对象,从而实现响应式。Proxy可以直接监听对象和数组的变化,不需要遍历整个对象,性能会更好。

优点:

  • 可以监听到对象属性的变化
  • 可以监听到数组的变化
  • 浅层监听,只有当对象的深层属性被使用到时才会递归遍历监听。减少了性能开销。

vue3的proxy优势

  • proxy性能整体上优于Object.defineProperty
  • vue3支持更多数据类型的劫持(vue2只支持Object、Array;vue3支持Object、Array、Map、WeakMap、Set、WeakSet)
  • vue3支持更多时机来进行依赖收集和触发通知(vue2只在get时进行依赖收集,vue3在get/has/iterate时进行依赖收集;vue2只在set时触发通知,vue3在set/add/delete/clear时触发通知),所以vue2中的响应式缺陷vue3可以实现
  • vue3做到了“精准数据”的数据劫持(vue2会把整个data进行递归数据劫持,而vue3只有在用到某个对象时,才进行数据劫持,所以响应式更快并且占内存更小)
  • vue3的依赖收集器更容易维护(vue3监听和操作的是原生数组;vue2是通过重写的方法实现对数组的监控)

Vue3 的 Diff 优化策略

1.靶向更新(Targeted Updates)

  • 补丁标志(Patch Flags) 在编译阶段分析模板,为动态绑定的节点添加标记(如 TEXT、CLASS、PROPS),标记其动态部分类型。 效果:Diff 时只需检查标记的动态属性,跳过静态内容。

  • 静态提升(Static Hoisting)
    将静态节点(无动态绑定)提取到渲染函数外部,避免重复创建 VNode。
    效果:减少内存占用和 Diff 时的比对次数。

  1. 块树(Block Tree)与动态锚点

块(Block) 将模板中的动态节点(含 v-if、v-for 或动态插槽)包裹为「块」,每个块追踪其内部动态子节点。

动态锚点 在块内记录动态节点的位置,更新时直接定位到动态部分,跳过静态结构。

效果:Diff 范围从整棵树缩小到块内动态节点,复杂度从 O(n) 降低到 O(动态节点数)。

  1. 最长递增子序列(LIS)优化列表更新

问题场景:当列表元素顺序变化时,Vue2 的双端比较可能导致多次 DOM 移动。

Vue3 解决方案: 在确定新旧子节点映射后,使用 最长递增子序列算法 找到最少的 DOM 移动路径。

示例: 新旧子节点索引序列为 [2, 3, 1, 4] → LIS 为 [2, 3, 4],只需移动节点 1 到正确位置。

效果:最小化 DOM 移动次数,提升列表渲染性能。

6 vue3 hooks

Hooks是一种特殊的函数,它可以让你在函数组件中使用状态和其他Vue特性。Vue3中的Hooks通常具有以下特征:

  • 以"use"作为函数名称前缀
  • 返回一个包含响应式数据或方法的对象
  • 可以使用其他Hooks
  • 遵循组合式函数的设计模式

与 Vue2 Mixins 的对比

对比项Vue3 HooksVue2 Mixins
逻辑隔离闭包封装,状态独立共享上下文,易命名冲突
代码组织函数式,逻辑集中分散在多个选项(data、methods)
调试友好性状态来源清晰,易追踪数据来源隐式,调试困难

使用案例: blog.csdn.net/BillKu/arti…

7 createApp 是 Vue3 应用创建的入口函数,用于生成 Vue 应用实例,是 Vue3 官方推荐的应用初始化方式

Vue3 实现了多实例隔离机制,在Vue2(new Vue())中只能有一个全局唯一的应用实例,只能挂载到一个根元素,并且全局共享配置。但是在vue3(createApp())中允许多实例共存 ,每个实例独立配置(组件/插件/指令)并且每个实例可挂载到不同容器 ‌。

8 pinia

vuex与pinia区别:

  1. ‌API设计与使用方式‌:
  • ‌Vuex‌: 需要定义state、mutations、actions和getters四个核心概念: 同步状态修改必须通过mutations提交。 异步操作需在actions中通过commit触发mutations‌‌
  • ‌Pinia‌: 简化API流程,直接通过actions同步/异步修改state,无需mutations,代码量减少40%。‌‌ ‌
  1. TypeScript支持‌:
  • ‌Vuex‌: 类型系统复杂,需手动定义类型装饰器,在大型项目中维护成本较高。‌‌
  • ‌Pinia‌: 原生支持类型推断,可直接通过this访问状态并自动推导类型,类型安全性和开发效率更高。‌‌
  1. ‌模块化管理‌:
  • ‌Vuex‌: 采用命名空间模块化设计,嵌套结构易导致代码复杂度增加,模块间依赖管理困难。‌‌
  • ‌Pinia‌: 每个store独立存在,支持扁平化组合式模块管理,通过defineStore实现天然代码分割。‌‌

‌注意:storeToRefs ‌是 Pinia 提供的一个工具函数,用于在 Vue 3 的组合式API中解构Store的状态(state)或计算属性(getters)时保持响应性。

import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const { name, age, isAdult } = storeToRefs(userStore)

详细案例:juejin.cn/post/705743…

9 vue3数据传输方式

一、父子组件通信

  1. Props(父 → 子),父组件通过属性绑定传递数据,子组件用 defineProps 接收。
<!-- 父组件 -->
<Child :message="parentMsg" />

<!-- 子组件 -->
<script setup>
const props = defineProps({ message: String });
</script>
  1. 自定义事件(子 → 父),子组件通过 defineEmits 定义事件,父组件监听事件接收数据。
<!-- 子组件 -->
<button @click="$emit('update', data)">提交</button>
<script setup>
const emit = defineEmits(['update']);
</script>

<!-- 父组件 -->
<Child @update="handleUpdate" />
  1. v-model 双向绑定,简化父子组件双向数据同步,可支持多个 v-model 绑定。
<!-- 父组件 -->
<Child v-model:title="pageTitle" />

<!-- 子组件 -->
<script setup>
const props = defineProps(['title']);
const emit = defineEmits(['update:title']);
</script>

二、跨层级组件通信

  1. provide / inject,祖先组件提供数据,后代组件注入使用,适合深层嵌套场景。
<!-- 祖先组件 -->
<script setup>
import { provide } from 'vue';
provide('key', data);
</script>

<!-- 后代组件 -->
<script setup>
import { inject } from 'vue';
const data = inject('key');
</script>
  1. 全局事件总线(mitt),使用第三方库(如 mitt)实现任意组件间事件通信。
// 事件总线实例
import mitt from 'mitt';
const bus = mitt();

// 组件A发射事件
bus.emit('event', data);

// 组件B监听事件
bus.on('event', (data) => { ... });

(后续内容保持相同结构,此处省略完整展示...)

mitt使用拓展:blog.csdn.net/Jacklx888/a…

三、模板引用与直接访问‌

‌1.$refs$parent‌,通过模板引用直接访问组件实例或父组件(较少推荐,破坏封装性)。

<!-- 父组件 -->
<Child ref="childRef" />
<script setup>
import { ref } from 'vue';
const childRef = ref();
childRef.value.childMethod(); // 调用子组件方法
</script>

‌2.$attrs 透传属性 自动继承未在 props 中声明的父组件属性(如 classstyle)。

<!-- 父组件 -->
<Child type="primary" />

<!-- 子组件 -->
<div v-bind="$attrs">...</div> <!-- 透传到div -->

四、路由传参*

1.Query 参数‌ ,通过 URL 查询字符串传递(?key=value),用 route.query 获取。

// 跳转时传参
router.push({ path: '/user', query: { id: 1 } });

// 目标组件获取
import { useRoute } from 'vue-router';
const route = useRoute();
console.log(route.query.id); // 1

2.Params 动态路由‌ ,定义动态路由路径,通过 route.params 获取。

// 路由定义
{ path: '/user/:id', component: User }

// 跳转传参
router.push({ path: '/user/1' });

// 获取参数
const { id } = useRoute().params;

3.‌Props 解耦路由参数‌,将路由参数映射为组件的 props。

// 路由配置
{ path: '/user/:id', component: User, props: true }

// 组件直接使用 props
defineProps(['id']);

‌五、全局状态管理‌

‌Pinia(推荐)‌,集中式状态管理,支持响应式数据和模块化。

// store/userStore.js
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
  state: () => ({ name: 'Alice' }),
  actions: { updateName(newName) { this.name = newName; } }
});

// 组件中使用
import { useUserStore } from '@/stores/userStore';
const userStore = useUserStore();
console.log(userStore.name); // Alice

六、其他辅助方式

Slot 插槽

<!-- 父组件 -->
<Child><template #header>标题</template></Child>

<!-- 子组件 -->
<slot name="header"></slot>

选择建议‌

  • ‌父子组件‌:优先 props + emit 事件 或 v-model。
  • ‌跨层级‌:provide/inject 或 Pinia。
  • ‌全局共享‌:Pinia。
  • ‌路由参数‌:Query 或 Params 按需使用。
  • ‌事件广播‌:mitt 库替代已废弃的 on/on/off。

10 打包工具区别

vite由2部分构成:

  • 开发服务器:基于原生ESModule提供丰富功能
  • 构建命令:使用Rollup打包代码

拓展点: Rollup 是面向现代 ES6 模块标准的轻量级打包工具,核心特性如下:

    1.  模块化优势:

        -   支持 Tree Shaking 剔除未使用代码,生成更精简的代码包;‌‌3‌
        -   输出格式灵活,支持 CommonJS、UMD、ES 等模块规范。‌‌

    2.  使用场景对比:

        -   Rollup 适用:框架类库开发(如 Vue、React),需高可读性代码和轻量化构建;‌‌4‌
        -   Webpack 适用:前端工程化场景,需处理 CSS、HTML 及复杂代码拆分。‌‌

vite使用vite.config.js文件配置 具体配置项:blog.csdn.net/qq_43258522…

vite和webpack区别:

  • webpack会先打包,然后启动开发服务器,请求服务器时直接给予打包结果。
  • vite使用ES Modules开发,由于现代浏览器本身就支持ES Modules,所以直接启动开发服务器,请求哪个模块再对该模块进行实时编译,在HRM(热模块更新)方面,当某个模块内容改变时,让浏览器去重新请求该模块即可,而不用像webpack那样把该模块的所有依赖重新编译。

构建方式

  • Webpack 是一种静态模块化的构建工具。它通过解析入口文件的依赖关系,将所有模块打包成一个或多个输出文件
  • Vite它利用浏览器对 ES modules 的原生支持,开发阶段不需要打包,而是直接在浏览器中运行源代码。

实现原理

  • Webpack 的实现原理是将所有模块转换为一棵依赖图,用各种 loader 和 plugin 对模块进行处理和优化。Webpack 的核心功能是模块化打包和代码分割。
  • Vite 的实现原理是在浏览器端实现了一个简单的服务器,拦截浏览器请求,根据请求的 URL 动态生成对应的代码并响应给浏览器。

拓展点1:冷启动与热启动

  • 冷启动是指应用程序或服务首次启动的过程,或者在没有任何先前运行时状态的情况下启动的过程。
  • 热启动是指在应用程序或服务已经运行过一次之后,再次启动时的情况。

具体区别:blog.csdn.net/qq_40055200…