Skip to content

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 的区别 ​

特性reactiveref
适用类型对象/数组基本类型/对象/数组
响应式实现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 也能正确处理响应式更新

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 组件的创建与挂载流程 ​

  1. 创建组件实例:调用 createApp 创建应用实例
  2. 解析组件选项:处理组件的模板、数据、生命周期钩子等
  3. 生成渲染函数:将组件模板编译为渲染函数
  4. 执行渲染函数:生成虚拟 DOM 树
  5. patch 过程:将虚拟 DOM 转换为真实 DOM,并挂载到页面

4.2 组件的更新流程 ​

  1. 响应式数据变化:触发依赖更新
  2. 重新执行渲染函数:生成新的虚拟 DOM 树
  3. diff 算法:比较新旧虚拟 DOM 树的差异
  4. patch 更新:将差异应用到真实 DOM

4.3 组件的卸载流程 ​

  1. 触发卸载钩子:执行 onBeforeUnmount 和 onUnmounted
  2. 清理副作用:取消定时器、清除事件监听等
  3. 移除 DOM:将组件从 DOM 中移除
  4. 释放资源:清理组件实例和相关资源

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 router

10.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 中仍然支持过渡动画
  • 使用 &lttransition&gt 和 &lttransition-group&gt 组件
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 中仍然支持插槽
  • 使用 &ltslot&gt 组件
  • 默认插槽:
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 需要掌握其核心概念和最佳实践,不断实践和总结经验,才能在实际开发中灵活运用。

Released under the MIT License.