Vue3 高频面试题(上)
1. Vue3 与 Vue2 的核心区别
1.1 响应式系统
- Vue2:使用
Object.defineProperty劫持对象属性的 getter 和 setter - Vue3:使用
Proxy代理整个对象,能更好地支持动态属性的添加和删除 - 优势:
- Vue3 能自动检测对象属性的添加和删除
- Vue3 对数组的响应式处理更高效,无需重写数组方法
- Vue3 支持 Map、Set 等数据结构
1.2 组合式 API
- Vue2:使用 Options API,将逻辑分散在 data、methods、computed 等选项中
- Vue3:引入 Composition API,通过 setup 函数将相关逻辑聚合在一起
- 优势:
- 更好的逻辑组织和复用
- 更好的 TypeScript 支持
- 更灵活的代码结构
1.3 编译优化
- Vue3:引入了静态提升、PatchFlag、Block Tree 等编译优化
- 性能提升:
- 静态节点提升到渲染函数外部,避免每次渲染都重新创建
- PatchFlag 标记动态节点,diff 时只比较有变化的部分
- Block Tree 将动态节点分组,提高 diff 效率
2. 响应式系统
2.1 Proxy 和 Reflect 的作用
- Proxy:用于创建对象的代理,拦截对象的各种操作
- Reflect:与 Proxy 配合使用,保持与目标对象行为的一致性
- receiver 参数:确保在访问对象属性时,
this始终指向代理对象而非原始对象
javascript
const proxy = new Proxy(target, {
get(target, key, receiver) {
// receiver 确保 getter 中的 this 指向代理
return Reflect.get(target, key, receiver)
}
})2.2 reactive 与 ref 的区别
| 特性 | reactive | ref |
|---|---|---|
| 适用类型 | 对象/数组 | 基本类型/对象/数组 |
| 响应式实现 | Proxy 代理 | 包装对象({ value: ... }) |
| 访问方式 | 直接访问属性 | 通过 .value 访问 |
| 类型推断 | 保持原始类型 | 自动类型推断 |
| 解构赋值 | 会失去响应式 | 需使用 toRefs 保持响应式 |
2.3 computed 与 watch 的区别
- computed:
- 基于依赖缓存,只有依赖变化时才重新计算
- 必须有返回值
- 适用于计算派生值
- watch:
- 监听一个或多个响应式数据的变化
- 可以执行异步操作
- 适用于副作用操作
2.4 watch 与 watchEffect 的区别
- watch:
- 需要明确指定监听的数据源
- 可以访问新值和旧值
- 支持深度监听和立即执行
- watchEffect:
- 自动追踪函数内部使用的响应式数据
- 只能访问新值,无法访问旧值
- 立即执行,并且在依赖变化时重新执行
2.5 如何处理响应式数据的循环引用
- Vue3 使用
WeakMap存储响应式对象的依赖关系 - WeakMap 的键是弱引用,不会阻止垃圾回收
- 自动处理对象的循环引用,避免内存泄漏
2.6 数组响应式的特殊处理
- Vue2:通过重写数组方法(push、pop、shift、unshift、splice、sort、reverse)实现响应式
- Vue3:利用 Proxy 的
set陷阱,无需重写数组方法- 当调用数组方法时,会触发 Proxy 的
set陷阱 - Vue3 能自动检测数组长度变化、元素新增和删除
- 对于索引直接赋值(如 arr[0] = 1),Vue3 也能正确处理响应式更新
- 当调用数组方法时,会触发 Proxy 的
2.7 shallowRef 与 shallowReactive 的使用场景
- shallowRef:创建浅层响应式引用,只监听 .value 的变化,不监听对象内部属性的变化
- shallowReactive:创建浅层响应式对象,只监听对象第一层属性的变化
- 使用场景:
- 处理大型对象,避免深度响应式转换的性能开销
- 处理不需要深度响应式的数据
2.8 markRaw 的作用
markRaw用于标记一个对象,使其永远不会被转换为响应式对象- 使用场景:
- 处理不需要响应式的第三方库实例
- 优化性能,避免不必要的响应式转换
2.9 toRef 与 toRefs 的区别
- toRef:将对象的单个属性转换为响应式引用
- toRefs:将对象的所有属性转换为响应式引用
- 使用场景:
- toRef 用于处理对象的单个属性
- toRefs 用于解构响应式对象,保持响应式
javascript
const state = reactive({ name: 'Vue', age: 3 })
const { name, age } = toRefs(state) // name 和 age 保持响应式3. 组合式 API
3.1 setup 函数的作用
- setup 是组合式 API 的入口函数
- 在组件创建前执行,执行顺序早于 beforeCreate
- 接收两个参数:props 和 context
- 返回的对象会被合并到组件实例中
3.2 setup 函数的参数
- props:响应式的组件属性,当 props 变化时会自动更新
- context:包含 attrs、emit、slots 等组件上下文信息
- attrs:非响应式的属性集合
- emit:用于触发组件事件
- slots:组件的插槽内容
3.3 生命周期钩子的变化
- Vue3 中,生命周期钩子需要从 vue 中导入并在 setup 中使用
- 命名方式发生变化,前缀为 on
- 对应关系:
- beforeCreate → 无(setup 中执行)
- created → 无(setup 中执行)
- beforeMount → onBeforeMount
- mounted → onMounted
- beforeUpdate → onBeforeUpdate
- updated → onUpdated
- beforeUnmount → onBeforeUnmount
- unmounted → onUnmounted
- errorCaptured → onErrorCaptured
- renderTracked → onRenderTracked
- renderTriggered → onRenderTriggered
3.4 provide 与 inject 的使用
- provide 用于在父组件中提供数据
- inject 用于在子组件中注入数据
- 优势:
- 避免了 props 的层层传递
- 适用于深层组件间的通信
javascript
// 父组件
import { provide } from 'vue'
setup() {
provide('theme', 'dark')
}
// 子组件
import { inject } from 'vue'
setup() {
const theme = inject('theme')
return { theme }
}3.5 ref 与 reactive 的最佳实践
- ref:
- 用于基本类型数据
- 用于需要在模板中直接使用的响应式数据
- 用于需要作为 props 传递的响应式数据
- reactive:
- 用于对象或数组类型数据
- 用于组织多个相关的响应式数据
3.6 如何在 setup 中访问组件实例
- 在 Vue3 中,setup 函数中无法直接访问 this
- 可以使用 getCurrentInstance 获取组件实例
javascript
import { getCurrentInstance } from 'vue'
setup() {
const instance = getCurrentInstance()
// instance.appContext.config.globalProperties 可访问全局属性
}3.7 自定义 Composition Function
- 自定义 Composition Function 是组合式 API 的核心优势
- 用于封装可复用的逻辑
- 命名通常以 use 开头
javascript
// useCounter.js
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => count.value++
const decrement = () => count.value--
return { count, increment, decrement }
}
// 使用
setup() {
const { count, increment } = useCounter(10)
return { count, increment }
}4. 组件与生命周期
4.1 组件的创建与挂载流程
- 创建组件实例:调用
createApp创建应用实例 - 解析组件选项:处理组件的模板、数据、生命周期钩子等
- 生成渲染函数:将组件模板编译为渲染函数
- 执行渲染函数:生成虚拟 DOM 树
- patch 过程:将虚拟 DOM 转换为真实 DOM,并挂载到页面
4.2 组件的更新流程
- 响应式数据变化:触发依赖更新
- 重新执行渲染函数:生成新的虚拟 DOM 树
- diff 算法:比较新旧虚拟 DOM 树的差异
- patch 更新:将差异应用到真实 DOM
4.3 组件的卸载流程
- 触发卸载钩子:执行 onBeforeUnmount 和 onUnmounted
- 清理副作用:取消定时器、清除事件监听等
- 移除 DOM:将组件从 DOM 中移除
- 释放资源:清理组件实例和相关资源
4.4 异步组件的使用
- Vue3 中使用
defineAsyncComponent定义异步组件 - 支持懒加载组件,优化初始加载性能
javascript
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent({
loader: () => import('./MyComponent.vue'),
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
delay: 200,
timeout: 3000
})4.5 动态组件的使用
- 使用
<component :is="componentName"></component>实现动态组件 - componentName 可以是组件名、组件对象或异步组件
4.6 递归组件的使用
- 组件可以在自己的模板中调用自己
- 需要设置递归结束条件,避免无限递归
vue
<template>
<div>
<h3>{{ item.title }}</h3>
<div v-if="item.children">
<TreeItem
v-for="child in item.children"
:key="child.id"
:item="child"
/>
</div>
</div>
</template>
<script setup>
const props = defineProps(['item'])
</script>4.7 组件通信方式
- props / emit:父子组件通信
- provide / inject:跨层级组件通信
- 事件总线:使用 mitt 库实现组件通信
- 状态管理:使用 Pinia 或 Vuex 管理全局状态
4.8 事件总线的替代方案
- Vue2 中使用 Vue 实例作为事件总线
- Vue3 中推荐使用 mitt 库
- 使用方式:
javascript
// eventBus.js
import mitt from 'mitt'
export const emitter = mitt()
// 发送事件
import { emitter } from './eventBus'
emitter.emit('event-name', data)
// 监听事件
import { emitter } from './eventBus'
const off = emitter.on('event-name', (data) => {
console.log(data)
})
// 取消监听
off()
// 或 emitter.off('event-name')4.9 组件的 v-model
- Vue3 中,组件的 v-model 支持多个属性
- 默认使用 modelValue 作为 prop,update:modelValue 作为事件
- 可以自定义 v-model 的 prop 和事件
vue
<!-- 父组件 -->
<MyComponent v-model="value" />
<!-- 子组件 -->
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>4.10 组件的 slots
- 默认插槽:
<slot></slot> - 命名插槽:
<slot name="header"></slot> - 作用域插槽:
<slot :data="data"></slot>
vue
<!-- 父组件 -->
<MyComponent>
<template #header>
<h1>Header</h1>
</template>
<template #default="scope">
<p>{{ scope.data }}</p>
</template>
</MyComponent>
<!-- 子组件 -->
<template>
<div>
<slot name="header"></slot>
<slot :data="data"></slot>
</div>
</template>
<script setup>
const data = ref('Hello World')
</script>5. 模板语法
5.1 v-if 与 v-show 的区别
- v-if:条件渲染,条件为 false 时,元素会被从 DOM 中移除
- v-show:条件显示,条件为 false 时,元素会被添加 display: none 样式
- 使用场景:
- v-if 适用于条件很少改变的场景
- v-show 适用于条件频繁改变的场景
5.2 v-for 与 v-if 的优先级
- Vue2:v-for 优先级高于 v-if
- Vue3:v-if 优先级高于 v-for
- 最佳实践:避免在同一元素上使用 v-for 和 v-if
- 可以在 v-for 的父元素上使用 v-if
- 可以使用 computed 对数据进行过滤后再使用 v-for
5.3 v-for 的 key 属性
- key 用于标识列表中的唯一元素
- 帮助 Vue 识别哪些元素被添加、删除或修改
- 提高 diff 算法的效率
- 最佳实践:使用唯一的 id 作为 key,避免使用索引作为 key
5.4 v-once 指令
- v-once 用于标记元素或组件只渲染一次
- 后续的更新不会重新渲染该元素
- 使用场景:渲染静态内容,优化性能
5.5 v-memo 指令
- v-memo 用于缓存元素或组件
- 当依赖的表达式没有变化时,不会重新渲染该元素
- 使用场景:优化大型列表的渲染性能
vue
<template>
<div v-memo="[list.length]">
<div v-for="item in list" :key="item.id">{{ item.name }}</div>
</div>
</template>5.6 v-pre 指令
- v-pre 用于跳过元素及其子元素的编译过程
- 使用场景:渲染纯文本内容,优化性能
5.7 v-cloak 指令
- v-cloak 用于在 Vue 实例挂载前隐藏未编译的模板
- 通常与 CSS 配合使用:
[v-cloak] { display: none; }
5.8 动态属性和动态类
- 动态属性:
<div :[attributeName]="value"></div> - 动态类:vue
<div :class="{ 'active': isActive, 'disabled': isDisabled }" ></div>
5.9 模板中的计算属性
- 模板中可以直接使用计算属性
- 避免在模板中进行复杂计算,影响性能
- 最佳实践:将复杂计算逻辑放在 computed 中
5.10 模板中的方法调用
- 模板中可以直接调用 methods 中的方法
- 注意事项:方法会在每次渲染时重新执行,避免在模板中调用耗时的方法
6. 编译优化
6.1 静态提升
- 静态提升是 Vue3 的编译优化特性
- 将静态节点提升到渲染函数外部
- 避免每次渲染都重新创建静态节点
javascript
// 编译前
<div>Static Text</div>
// 编译后
const _hoisted_1 = /*#__PURE__*/ createTextVNode("Static Text")
function render() {
return (_openBlock(), _createElementBlock("div", null, [
_hoisted_1
]))
}6.2 PatchFlag
- PatchFlag 是 Vue3 用于标记动态节点的标记
- 在 diff 过程中,Vue 可以根据 PatchFlag 快速定位需要更新的节点
- 跳过静态节点的比较,提高 diff 效率
6.3 Block Tree
- Block Tree 是 Vue3 引入的优化概念
- 将动态节点分组,形成 Block
- diff 时只需要遍历 Block 内的动态节点
- 提高 diff 算法的效率
6.4 缓存事件处理函数
- Vue3 会自动缓存事件处理函数
- 避免每次渲染都创建新的事件处理函数
- 提高性能,减少不必要的重渲染
6.5 编译时的 v-once 优化
- v-once 指令会被编译为静态节点
- 后续的更新不会重新渲染该节点
- 优化性能,适用于静态内容
7. 虚拟 DOM
7.1 虚拟 DOM 的作用
- 虚拟 DOM 是对真实 DOM 的轻量级描述
- 避免直接操作 DOM 带来的性能开销
- 支持跨平台渲染(Web、移动端、SSR 等)
- 提供了统一的编程模型,简化开发
7.2 Vue3 的 diff 算法
- Vue3 使用快速 diff 算法
- 基于 Block Tree 优化,只比较动态节点
- 采用双端比较策略,提高 diff 效率
7.3 VNode 的结构
- VNode 是虚拟 DOM 节点的表示
- 包含标签名、属性、子节点等信息
- 用于描述真实 DOM 的结构
7.4 渲染函数
- 渲染函数是生成虚拟 DOM 的函数
- Vue3 中,渲染函数使用 h 函数创建 VNode
- 使用方式:
javascript
import { h } from 'vue'
export default {
render() {
return h('div', { class: 'container' }, [
h('h1', 'Hello World'),
h('p', 'This is a Vue component')
])
}
}7.5 手动渲染函数的使用场景
- 处理复杂的动态组件结构
- 实现高性能的自定义组件
- 跨平台渲染
8. 性能优化
8.1 减少重渲染
- 使用 v-once:标记静态内容
- 使用 v-memo:缓存元素或组件
- 合理使用 computed:避免在模板中进行复杂计算
- 避免不必要的响应式转换:使用 markRaw
8.2 优化大型列表
- 使用虚拟列表:只渲染可视区域内的元素
- 提供 key 属性:帮助 Vue 识别元素
- 使用 computed 过滤数据:避免在模板中使用 v-if
- 使用 v-once:渲染静态列表
8.3 优化组件通信
- 使用 props / emit:避免过度使用 provide / inject
- 使用 Pinia 或 Vuex:管理全局状态
- 使用事件总线:避免组件间的直接依赖
8.4 优化异步组件
- 使用 defineAsyncComponent:懒加载组件
- 设置合理的加载状态:提升用户体验
- 使用 code splitting:分割代码,减少初始加载时间
8.5 优化资源加载
- 图片懒加载:只加载可视区域内的图片
- 代码分割:按需加载第三方库
- 使用 CDN:加速静态资源加载
8.6 优化构建配置
- 使用 Vite:提高开发和构建速度
- 配置 Tree Shaking:移除未使用的代码
- 配置代码压缩:减少打包体积
- 配置按需加载:只加载需要的功能
9. 状态管理
9.1 Pinia 与 Vuex 的区别
- Pinia:
- 简化的 API,无需 mutations
- 更好的 TypeScript 支持
- 自动代码分割
- 支持组合式 API
- 更轻量
- Vuex:
- 复杂的 API,需要 mutations
- 有限的 TypeScript 支持
- 集中式状态管理
9.2 Pinia 的核心概念
- Store:存储状态的容器
- State:存储的状态
- Getters:计算派生状态
- Actions:处理异步操作和业务逻辑
9.3 Pinia 的基本使用
javascript
// store/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
},
async incrementAsync() {
await new Promise(resolve => setTimeout(resolve, 1000))
this.count++
}
}
})
// 使用
import { useCounterStore } from '@/store/counter'
setup() {
const counter = useCounterStore()
counter.increment()
return { counter }
}9.4 Pinia 的组合式 API 写法
javascript
// store/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
async function incrementAsync() {
await new Promise(resolve => setTimeout(resolve, 1000))
count.value++
}
return { count, doubleCount, increment, incrementAsync }
})9.5 Pinia 的插件系统
- Pinia 支持插件系统,可以扩展 Pinia 的功能
- 可以用于添加全局状态、持久化存储等
9.6 Vuex 的核心概念
- State:存储的状态
- Getters:计算派生状态
- Mutations:修改状态的同步函数
- Actions:处理异步操作的函数
- Modules:模块化状态管理
9.7 Vuex 的基本使用
javascript
// store/index.js
import { createStore } from 'vuex'
export default createStore({
state: {
count: 0
},
getters: {
doubleCount: state => state.count * 2
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
incrementAsync({ commit }) {
return new Promise(resolve => {
setTimeout(() => {
commit('increment')
resolve()
}, 1000)
})
}
},
modules: {}
})9.8 如何在组件中使用 Vuex
javascript
// Vue2
this.$store.state.count
this.$store.commit('increment')
this.$store.dispatch('incrementAsync')
// Vue3
import { useStore } from 'vuex'
setup() {
const store = useStore()
const count = computed(() => store.state.count)
const increment = () => store.commit('increment')
const incrementAsync = () => store.dispatch('incrementAsync')
return { count, increment, incrementAsync }
}9.9 Vuex 的模块化
- 用于将大型状态管理拆分为多个模块
- 每个模块可以拥有自己的 state、getters、mutations 和 actions
javascript
// store/modules/user.js
export default {
namespaced: true,
state: () => ({
name: 'Vue'
}),
getters: {
welcome: state => `Hello ${state.name}`
},
mutations: {
setName(state, name) {
state.name = name
}
},
actions: {
updateName({ commit }, name) {
commit('setName', name)
}
}
}
// store/index.js
import { createStore } from 'vuex'
import user from './modules/user'
export default createStore({
modules: {
user
}
})
// 使用
store.state.user.name
store.commit('user/setName', 'Vue3')
store.dispatch('user/updateName', 'Vue3')10. 路由管理
10.1 Vue Router 4 的新特性
- 组合式 API:支持在 setup 中使用路由
- TypeScript 支持:更好的类型推断
- 动态路由:更灵活的路由配置
- 导航守卫:支持异步组件
10.2 路由的基本配置
javascript
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue')
}
]
})
export default router10.3 路由懒加载
- 使用动态 import 语法实现路由懒加载
- 减少初始加载时间
- 提高应用性能
javascript
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue')
}
]
})10.4 嵌套路由
- 用于实现页面的层级结构
- 使用 children 配置嵌套路由
javascript
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/user',
name: 'user',
component: UserLayout,
children: [
{
path: '',
name: 'user.index',
component: UserIndex
},
{
path: ':id',
name: 'user.detail',
component: UserDetail
}
]
}
]
})10.5 动态路由
- 使用 :id 语法定义动态路由参数
- 使用 route.params 访问路由参数
10.6 路由守卫
- 全局守卫:router.beforeEach、router.afterEach
- 路由独享守卫:beforeEnter
- 组件内守卫:onBeforeRouteEnter、onBeforeRouteUpdate、onBeforeRouteLeave
10.7 路由导航的方式
- 声明式导航:使用
<router-link>组件 - 编程式导航:使用 router.push、router.replace、router.go 等方法
10.8 路由元信息
- 用于在路由配置中添加自定义数据
- 可以在路由守卫中访问
javascript
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/admin',
name: 'admin',
component: AdminView,
meta: {
requiresAuth: true,
roles: ['admin']
}
}
]
})
// 路由守卫
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth) {
// 检查登录状态
}
next()
})10.9 路由的滚动行为
- 用于控制路由切换时的滚动位置
javascript
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { top: 0 }
}
},
routes: []
})11. 工具函数
11.1 defineProps 的使用
- 用于定义组件的 props
- 支持类型定义和默认值
javascript
// 基本使用
const props = defineProps(['name', 'age'])
// 带类型和默认值
const props = defineProps({
name: String,
age: {
type: Number,
default: 18
}
})11.2 defineEmits 的使用
- 用于定义组件的事件
- 支持类型定义
javascript
// 基本使用
const emit = defineEmits(['update:modelValue', 'change'])
// 带类型定义
const emit = defineEmits({
'update:modelValue': (value) => typeof value === 'string',
'change': (id, value) => typeof id === 'number' && typeof value === 'string'
})11.3 defineExpose 的使用
- 用于暴露组件的属性和方法
- 允许父组件通过 ref 访问子组件的属性和方法
javascript
const count = ref(0)
const increment = () => count.value++
defineExpose({
count,
increment
})11.4 withDefaults 的使用
- 用于为 props 设置默认值
- 支持复杂类型的默认值
javascript
import { withDefaults } from 'vue'
const props = withDefaults(defineProps({
name: String,
user: {
type: Object,
required: true
},
list: Array
}), {
name: 'Vue',
list: () => []
})12. 错误处理
12.1 全局错误处理
- 使用 app.config.errorHandler 处理全局错误
javascript
app.config.errorHandler = (err, instance, info) => {
console.error('全局错误:', err)
console.error('组件实例:', instance)
console.error('错误信息:', info)
// 可以在这里上报错误信息
}12.2 组件内错误处理
- 使用 onErrorCaptured 生命周期钩子捕获子组件的错误
javascript
setup() {
onErrorCaptured((err, instance, info) => {
console.error('组件错误:', err)
console.error('组件实例:', instance)
console.error('错误信息:', info)
// 返回 true 可以阻止错误继续传播
return false
})
}12.3 异步错误处理
- 使用 try/catch 捕获异步操作的错误
- 使用 Promise.catch 捕获 Promise 的错误
javascript
async function fetchData() {
try {
const res = await axios.get('/api/data')
return res.data
} catch (err) {
console.error('请求错误:', err)
// 可以在这里处理错误
}
}13. 工具库
13.1 mitt 库
- 用于实现事件总线
- 轻量级,体积小
- 使用方式:
javascript
import mitt from 'mitt'
const emitter = mitt()
// 发送事件
emitter.emit('event-name', data)
// 监听事件
emitter.on('event-name', (data) => {
console.log(data)
})
// 取消监听
emitter.off('event-name')13.2 lodash 库
- 提供了丰富的工具函数
- 用于处理数组、对象、字符串等
- 常用函数:
- debounce:防抖
- throttle:节流
- cloneDeep:深拷贝
- merge:合并对象
- filter:过滤数组
- map:映射数组
13.3 axios 库
- 用于发送 HTTP 请求
- 支持 Promise API
- 支持拦截器、取消请求等功能
javascript
import axios from 'axios'
// 创建 axios 实例
const instance = axios.create({
baseURL: 'https://api.example.com',
timeout: 1000
})
// 请求拦截器
instance.interceptors.request.use(config => {
// 可以在这里添加 token 等信息
return config
}, error => {
return Promise.reject(error)
})
// 响应拦截器
instance.interceptors.response.use(response => {
return response.data
}, error => {
return Promise.reject(error)
})
// 发送请求
instance.get('/data')
.then(data => console.log(data))
.catch(error => console.error(error))14. 最佳实践
14.1 组件设计原则
- 单一职责:每个组件只负责一个功能
- 可复用性:设计通用组件,提高开发效率
- 可测试性:组件逻辑清晰,易于测试
- 可维护性:代码结构清晰,易于维护
14.2 代码组织
- 按功能组织:将相关的组件、工具函数等放在一起
- 模块化设计:将应用拆分为多个功能模块
- 分层架构:表示层、业务逻辑层、数据层
14.3 命名规范
- 组件命名:使用 PascalCase 命名组件
- 文件命名:使用 kebab-case 命名文件
- 变量命名:使用 camelCase 命名变量
- 常量命名:使用 UPPER_CASE 命名常量
14.4 TypeScript 最佳实践
- 使用类型定义:为组件的 props、emit 等添加类型定义
- 使用接口:定义复杂数据结构的类型
- 使用泛型:提高代码的复用性
- 使用类型断言:在必要时使用类型断言
14.5 性能优化最佳实践
- 减少重渲染:合理使用 v-once、v-memo 等指令
- 优化大型列表:使用虚拟列表
- 优化资源加载:图片懒加载、代码分割等
- 优化组件通信:避免不必要的组件依赖
15. 常见问题与解决方案
15.1 如何在 Vue3 中使用 Vue2 的插件
- 方式一:使用 Vue2 兼容模式
- 方式二:修改插件,使其支持 Vue3
- 方式三:寻找 Vue3 版本的替代插件
15.2 如何处理 Vue3 中的兼容问题
- 使用 @vue/compat:提供 Vue2 兼容模式
- 逐步迁移:将组件逐个迁移到 Vue3
- 测试:确保迁移后的代码能正常运行
15.3 如何在 Vue3 中使用全局属性
- 使用 app.config.globalProperties 添加全局属性
javascript
app.config.globalProperties.$http = axios
app.config.globalProperties.$formatDate = (date) => new Date(date).toLocaleDateString()15.4 如何在 Vue3 中使用全局组件
- 使用 app.component 注册全局组件
javascript
import MyComponent from './components/MyComponent.vue'
app.component('MyComponent', MyComponent)15.5 如何在 Vue3 中使用自定义指令
- 使用 app.directive 注册自定义指令
javascript
app.directive('focus', {
mounted(el) {
el.focus()
}
})
// 在模板中使用
<input v-focus />15.6 如何在 Vue3 中使用混入
- Vue3 仍然支持混入
- 但推荐使用组合式 API 替代混入
- 使用方式:
javascript
// mixin.js
export const myMixin = {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
}
}
// 组件中使用
import { defineComponent } from 'vue'
import { myMixin } from './mixin'
export default defineComponent({
mixins: [myMixin],
setup() {
// ...
}
})15.7 如何在 Vue3 中使用过滤器
- Vue3 中移除了过滤器
- 推荐使用 computed 或方法替代过滤器
vue
<!-- Vue2 -->
<div>{{ date | formatDate }}</div>
<!-- Vue3 -->
<div>{{ formatDate(date) }}</div>
<script setup>
const formatDate = (date) => new Date(date).toLocaleDateString()
</script>15.8 如何在 Vue3 中使用过渡动画
- Vue3 中仍然支持过渡动画
- 使用 <transition> 和 <transition-group> 组件
vue
<template>
<transition name="fade">
<div v-if="show">Hello World</div>
</transition>
</template>
<script setup>
const show = ref(true)
</script>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
</style>15.9 如何在 Vue3 中使用插槽
- Vue3 中仍然支持插槽
- 使用 <slot> 组件
- 默认插槽:
vue
<!-- 子组件 -->
<template>
<div>
<slot></slot>
</div>
</template>
<!-- 父组件 -->
<template>
<ChildComponent>
<div>Hello World</div>
</ChildComponent>
</template>- 命名插槽:
vue
<!-- 子组件 -->
<template>
<div>
<slot name="header"></slot>
<slot name="content"></slot>
<slot name="footer"></slot>
</div>
</template>
<!-- 父组件 -->
<template>
<ChildComponent>
<template #header>
<h1>Header</h1>
</template>
<template #content>
<p>Content</p>
</template>
<template #footer>
<p>Footer</p>
</template>
</ChildComponent>
</template>- 作用域插槽:
vue
<!-- 子组件 -->
<template>
<div>
<slot :data="items"></slot>
</div>
</template>
<script setup>
const items = ref([1, 2, 3, 4, 5])
</script>
<!-- 父组件 -->
<template>
<ChildComponent>
<template #default="scope">
<div v-for="item in scope.data" :key="item">{{ item }}</div>
</template>
</ChildComponent>
</template>16. 扩展知识
16.1 Vue3 的编译目标
- Vue3 支持编译为 ES5 或 ES6+
- 可以通过配置选择编译目标
- 配置方式:
javascript
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
target: 'es2015'
}
})16.2 Vue3 的 Tree Shaking
- Vue3 支持 Tree Shaking
- 只打包使用到的功能
- 减少打包体积
16.3 Vue3 的国际化
- 使用 vue-i18n 实现国际化
- 使用方式:
javascript
import { createI18n } from 'vue-i18n'
const messages = {
en: {
message: {
hello: 'Hello World'
}
},
zh: {
message: {
hello: '你好,世界'
}
}
}
const i18n = createI18n({
legacy: false,
locale: 'zh',
messages
})
app.use(i18n)16.4 Vue3 的测试
- 单元测试:使用 Vitest 或 Jest
- 组件测试:使用 Vue Test Utils
- E2E 测试:使用 Cypress 或 Playwright
16.5 Vue3 的构建工具
- Vite:快速的前端构建工具
- Webpack:成熟的前端构建工具
- Rollup:用于构建库的前端构建工具
17. 总结
Vue3 是 Vue.js 的重大升级,带来了许多新特性和性能优化:
- 响应式系统:使用 Proxy 和 Reflect 实现更高效的响应式
- 组合式 API:提供更灵活的代码组织方式
- 编译优化:静态提升、PatchFlag、Block Tree 等
- 虚拟 DOM:更高效的 diff 算法
- 性能优化:减少重渲染、优化资源加载等
学习 Vue3 需要掌握其核心概念和最佳实践,不断实践和总结经验,才能在实际开发中灵活运用。