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 支持(自动补全、错误提示)。
- 详解: blog.csdn.net/qq_38060125…
- 装饰符: blog.csdn.net/qq_45739157…
- interface和type区别: blog.csdn.net/2301_818545…
拓展点1: any与unknown区别
TypeScript中any和unknown的核心区别在于类型安全性: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]) => {
// 处理变化逻辑
})
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()函数中手动返回暴露的内容。 - 适合需要明确控制暴露内容的场景。
<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 default | export |
|---|---|---|
| 导出次数 | 仅一次 | 多次 |
| 导入语法 | 无需花括号,变量名可自定义 | 需花括号,按名称匹配或使用as别名 |
| 典型场景 | 模块主功能、Vue组件 | 工具函数、常量等需按需导入的成员 |
注意事项
- 值类型:export default导出的是值的拷贝(原始类型)或引用(对象类型),取决于具体数据类型。
- 错误示例:避免在代码块(如函数内)使用export default,需保持模块顶层作用域。
4 生命周期变化
| 阶段 | 组合式 API 钩子 | 选项式 API 钩子 | 调用时机说明 |
|---|---|---|---|
| 创建阶段 | setup() | beforeCreate/created | 组合式 API 中 setup() 替代了 Vue 2 的这两个钩子,统一处理初始化逻辑 |
| 挂载阶段 | onBeforeMount | beforeMount | 模板编译完成但未挂载到 DOM |
onMounted | mounted | DOM 挂载完成,可安全操作 DOM 元素 | |
| 更新阶段 | onBeforeUpdate | beforeUpdate | 数据变化后,虚拟 DOM 重新渲染前触发 |
onUpdated | updated | 虚拟 DOM 重新渲染并应用补丁后触发 | |
| 销毁阶段 | onBeforeUnmount | beforeUnmount | 实例销毁前(Vue 2 的 beforeDestroy 更名,语义更清晰) |
onUnmounted | unmounted | 实例销毁后(Vue 2 的 destroyed 更名) |
关键说明:
- 命名变化:Vue 3 钩子名称更语义化(如
unmount替代destroy),组合式 API 需添加on前缀。 -
setup()整合:替代 Vue 2 的beforeCreate和created,集中处理响应式数据、方法等初始化逻辑。 - 选项式 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可以直接监听对象和数组的变化,不需要遍历整个对象,性能会更好。
- 引用数据类型:Proxy
- 基本数据类型:使用对象的属性来监听——> RefImpl(RefImpl 是 Vue3 中通过 ref() 创建的响应式引用对象内部实现类)
优点:
- 可以监听到对象属性的变化
- 可以监听到数组的变化
- 浅层监听,只有当对象的深层属性被使用到时才会递归遍历监听。减少了性能开销。
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 时的比对次数。
- 块树(Block Tree)与动态锚点
块(Block) 将模板中的动态节点(含 v-if、v-for 或动态插槽)包裹为「块」,每个块追踪其内部动态子节点。
动态锚点 在块内记录动态节点的位置,更新时直接定位到动态部分,跳过静态结构。
效果:Diff 范围从整棵树缩小到块内动态节点,复杂度从 O(n) 降低到 O(动态节点数)。
- 最长递增子序列(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 Hooks | Vue2 Mixins |
|---|---|---|
| 逻辑隔离 | 闭包封装,状态独立 | 共享上下文,易命名冲突 |
| 代码组织 | 函数式,逻辑集中 | 分散在多个选项(data、methods) |
| 调试友好性 | 状态来源清晰,易追踪 | 数据来源隐式,调试困难 |
使用案例: blog.csdn.net/BillKu/arti…
7 createApp 是 Vue3 应用创建的入口函数,用于生成 Vue 应用实例,是 Vue3 官方推荐的应用初始化方式
Vue3 实现了多实例隔离机制,在Vue2(new Vue())中只能有一个全局唯一的应用实例,只能挂载到一个根元素,并且全局共享配置。但是在vue3(createApp())中允许多实例共存 ,每个实例独立配置(组件/插件/指令)并且每个实例可挂载到不同容器 。
8 pinia
vuex与pinia区别:
- API设计与使用方式:
- Vuex: 需要定义state、mutations、actions和getters四个核心概念: 同步状态修改必须通过mutations提交。 异步操作需在actions中通过commit触发mutations
- Pinia: 简化API流程,直接通过actions同步/异步修改state,无需mutations,代码量减少40%。
- TypeScript支持:
- Vuex: 类型系统复杂,需手动定义类型装饰器,在大型项目中维护成本较高。
- Pinia: 原生支持类型推断,可直接通过this访问状态并自动推导类型,类型安全性和开发效率更高。
- 模块化管理:
- 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)
9 vue3数据传输方式
一、父子组件通信
- Props(父 → 子),父组件通过属性绑定传递数据,子组件用
defineProps接收。
<!-- 父组件 -->
<Child :message="parentMsg" />
<!-- 子组件 -->
<script setup>
const props = defineProps({ message: String });
</script>
- 自定义事件(子 → 父),子组件通过
defineEmits定义事件,父组件监听事件接收数据。
<!-- 子组件 -->
<button @click="$emit('update', data)">提交</button>
<script setup>
const emit = defineEmits(['update']);
</script>
<!-- 父组件 -->
<Child @update="handleUpdate" />
- v-model 双向绑定,简化父子组件双向数据同步,可支持多个
v-model绑定。
<!-- 父组件 -->
<Child v-model:title="pageTitle" />
<!-- 子组件 -->
<script setup>
const props = defineProps(['title']);
const emit = defineEmits(['update:title']);
</script>
二、跨层级组件通信
- provide / inject,祖先组件提供数据,后代组件注入使用,适合深层嵌套场景。
<!-- 祖先组件 -->
<script setup>
import { provide } from 'vue';
provide('key', data);
</script>
<!-- 后代组件 -->
<script setup>
import { inject } from 'vue';
const data = inject('key');
</script>
- 全局事件总线(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 中声明的父组件属性(如 class、style)。
<!-- 父组件 -->
<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 库替代已废弃的 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:冷启动与热启动
- 冷启动是指应用程序或服务首次启动的过程,或者在没有任何先前运行时状态的情况下启动的过程。
- 热启动是指在应用程序或服务已经运行过一次之后,再次启动时的情况。