Vue3重点
Vue 3 面试题集
基础概念
1. Vue 3 相比 Vue 2 有哪些重大变化?
答案:
- 性能提升:Vue 3 重写了虚拟 DOM 实现,渲染性能提升约 1.3~2 倍,内存占用减少约 50%
- Composition API:新增组合式 API,提供更灵活的逻辑组织和复用方式
- TypeScript 支持:Vue 3 是用 TypeScript 重写的,提供了更好的类型推断
- Teleport 组件:允许将组件的内容传送到 DOM 的其他位置
- Fragments:组件可以有多个根节点
- Suspense:处理异步组件的新特性
- 响应式系统升级:使用 ES6 的 Proxy 代替 Object.defineProperty,解决了 Vue 2 中的数组和对象响应式问题
- 全局 API 改为应用实例调用:减少了全局污染
- 更好的 Tree-shaking 支持:减小打包体积
2. 什么是 Composition API?它解决了什么问题?
答案:
Composition API 是 Vue 3 引入的一种新的组件逻辑组织方式,它允许我们按照功能/关注点组织代码,而不是按照选项(data、methods、computed 等)。
解决的问题:
- 逻辑复用:相比于 Vue 2 的 mixins,Composition API 提供了更清晰、更灵活的逻辑复用方式
- 更好的类型推断:对 TypeScript 的支持更好
- 代码组织:相关功能的代码可以放在一起,而不是分散在不同的选项中
- 避免命名冲突:不同功能模块可以在各自的作用域中定义变量,避免了 mixins 中的命名冲突问题
1 | // Vue 2 的 Options API |
3. ref 和 reactive 有什么区别?什么情况下使用它们?
答案:
ref 和 reactive 都是用于创建响应式数据的 API,但它们有以下区别:
ref:
- 可以包装任何类型的值(基本类型和对象类型)
- 创建一个包含
.value
属性的响应式对象 - 在模板中使用时会自动解包(不需要
.value
) - 适合处理基本类型值(如字符串、数字、布尔值)
reactive:
- 只能用于对象类型(包括数组和普通对象)
- 直接返回原始对象的响应式代理
- 不能用于基本类型值
- 不能被重新赋值(会破坏响应性)
使用场景:
- 对于基本类型值(如 string、number、boolean),必须使用 ref
- 对于复杂对象,可以使用 reactive 或 ref
- 如果需要整体替换一个响应式对象,应该使用 ref
- 如果只需要修改对象的属性,可以使用 reactive
1 | import { ref, reactive } from 'vue' |
4. Vue 3 中的生命周期钩子有哪些变化?
答案:
Vue 3 中的生命周期钩子与 Vue 2 相比有以下变化:
命名变化:
beforeCreate
和created
被setup()
函数本身替代- 其他钩子前缀改为
on
,如mounted
变为onMounted
使用方式:在 Composition API 中,生命周期钩子作为函数导入并在
setup()
中调用新增钩子:
onRenderTracked
:当组件渲染过程中追踪到响应式依赖时调用onRenderTriggered
:当响应式依赖触发组件重新渲染时调用
移除钩子:
beforeDestroy
改名为beforeUnmount
destroyed
改名为unmounted
1 | import { |
5. 什么是 Teleport?它解决了什么问题?
答案:
Teleport 是 Vue 3 新增的一个内置组件,它可以将组件的一部分 DOM 传送到组件 DOM 树之外的位置。
解决的问题:
- 解决了模态框、弹出菜单、通知等需要打破组件层次结构的 UI 元素的定位问题
- 避免了 CSS 样式(如 z-index、overflow、position)带来的限制
- 保持了组件的逻辑封装,同时允许 DOM 结构的灵活布局
1 | <template> |
响应式系统
6. Vue 3 的响应式系统是如何工作的?
答案:
Vue 3 的响应式系统基于 ES6 的 Proxy 实现,主要工作流程如下:
- 创建响应式对象:通过
reactive()
或ref()
创建响应式对象 - 代理拦截:使用 Proxy 拦截对象的属性访问、修改等操作
- 依赖追踪:当组件渲染或计算属性计算时,会访问响应式对象的属性,此时系统会记录这些依赖关系
- 变更通知:当响应式对象的属性被修改时,Proxy 的 set 处理器会被触发,然后通知所有依赖于该属性的副作用(如组件重新渲染)
1 | // 简化版的响应式系统实现原理 |
7. Vue 3 中如何实现计算属性?
答案:
Vue 3 中使用 computed()
函数来创建计算属性:
1 | import { ref, computed } from 'vue' |
计算属性具有以下特点:
- 基于其响应式依赖进行缓存
- 只有当依赖项变化时才会重新计算
- 返回一个只读的响应式引用
- 可以通过提供 get 和 set 函数创建可写的计算属性
8. watchEffect 和 watch 有什么区别?
答案:watchEffect
和 watch
都用于侦听响应式数据的变化并执行副作用,但它们有以下区别:
watchEffect:
- 立即执行一次回调函数,并自动追踪其中的响应式依赖
- 当任何依赖项变化时重新执行回调
- 不需要明确指定要侦听的数据源
- 无法获取被侦听状态的前一个值
watch:
- 默认情况下,只有在侦听的源数据变化时才执行回调
- 需要明确指定要侦听的数据源
- 可以访问被侦听状态的当前值和前一个值
- 支持侦听多个数据源
1 | import { ref, watch, watchEffect } from 'vue' |
组件通信
9. Vue 3 中组件之间有哪些通信方式?
答案:
Vue 3 中组件通信的主要方式包括:
Props 和 Events:
- 父组件通过 props 向子组件传递数据
- 子组件通过 emits 向父组件发送事件
v-model:
- Vue 3 中的 v-model 可以使用自定义的 prop 和事件
- 可以在同一组件上使用多个 v-model
provide/inject:
- 适用于深层组件嵌套的场景
- 祖先组件通过 provide 提供数据,后代组件通过 inject 注入数据
Vuex/Pinia:
- 集中式状态管理
- 适用于复杂应用的全局状态管理
mitt/tiny-emitter:
- 事件总线,用于任意组件间通信
- Vue 3 移除了 $on, $off 等事件 API,可以使用第三方库实现
1 | // Props 和 Events |
10. Vue 3 中的 v-model 有什么变化?
答案:
Vue 3 中的 v-model 相比 Vue 2 有以下变化:
默认 prop 和事件名称变化:
- Vue 2:prop 为
value
,事件为input
- Vue 3:prop 为
modelValue
,事件为update:modelValue
- Vue 2:prop 为
支持多个 v-model:
- Vue 3 允许在同一组件上使用多个 v-model,每个绑定可以有不同的名称
移除 .sync 修饰符:
- Vue 3 中 v-model 可以替代 Vue 2 中的 .sync 修饰符
自定义 v-model 修饰符:
- Vue 3 支持自定义 v-model 修饰符
1 | <!-- 基本用法 --> |
组件实现:
1 | // 单个 v-model |
性能优化
11. Vue 3 中如何优化性能?
答案:
Vue 3 中的性能优化方法包括:
使用 v-memo 减少不必要的重新渲染:
1
2
3<div v-memo="[item.id === selected]">
<!-- 只有当 item.id === selected 变化时才会重新渲染 -->
</div>使用 v-once 渲染静态内容:
1
2
3<div v-once>
<!-- 只渲染一次,之后不再更新 -->
</div>使用 computed 缓存计算结果:
1
2
3const filteredItems = computed(() => {
return items.value.filter(item => item.price > 100)
})使用 shallowRef 和 shallowReactive 减少深层响应:
1
2
3
4// 只有 state 的顶层属性是响应式的
const state = shallowReactive({
user: { name: 'John', address: { city: 'New York' } }
})使用 defineAsyncComponent 异步加载组件:
1
2
3const AsyncComponent = defineAsyncComponent(() =>
import('./components/AsyncComponent.vue')
)使用 KeepAlive 缓存组件实例:
1
2
3<keep-alive>
<component :is="currentComponent" />
</keep-alive>使用 Suspense 处理异步依赖:
1
2
3
4
5
6
7
8<suspense>
<template #default>
<async-component />
</template>
<template #fallback>
<loading-spinner />
</template>
</suspense>使用虚拟列表渲染大量数据:
1
2
3
4
5
6
7<virtual-list
:items="items"
:item-height="50"
v-slot="{ item }"
>
<div>{{ item.name }}</div>
</virtual-list>
12. Vue 3 中如何实现自定义指令?
答案:
Vue 3 中自定义指令的实现方式与 Vue 2 有所不同,主要变化在于钩子函数的名称与组件生命周期保持一致:
1 | // 全局注册 |
Vue 3 中自定义指令的钩子函数包括:
created
:在绑定元素的 attribute 或事件监听器被应用之前调用beforeMount
:在元素被插入到 DOM 之前调用mounted
:在绑定元素的父组件被挂载后调用beforeUpdate
:在包含组件的 VNode 更新之前调用updated
:在包含组件的 VNode 及其子组件的 VNode 更新之后调用beforeUnmount
:在绑定元素的父组件卸载之前调用unmounted
:当指令与元素解除绑定且父组件已卸载时调用
自定义指令的参数:
1 | app.directive('my-directive', (el, binding, vnode, prevVnode) => { |
工程化与生态
13. Vue 3 中如何使用 TypeScript?
答案:
Vue 3 对 TypeScript 有很好的支持,主要使用方式包括:
使用 defineComponent 包装组件选项:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import { defineComponent } from 'vue'
export default defineComponent({
props: {
message: {
type: String,
required: true
}
},
data() {
return {
count: 0
}
}
})在 setup 函数中使用类型注解:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import { defineComponent, ref, Ref } from 'vue'
interface User {
name: string;
age: number;
}
export default defineComponent({
setup() {
const count: Ref<number> = ref(0)
const user: Ref<User> = ref({ name: 'John', age: 30 })
return { count, user }
}
})使用 defineProps 和 defineEmits:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<script setup lang="ts">
// 使用运行时声明
const props = defineProps<{
name: string;
age?: number;
}>()
const emit = defineEmits<{
(e: 'update', id: number): void;
(e: 'delete'): void;
}>()
// 使用默认值
withDefaults(defineProps<{
name: string;
age?: number;
}>(), {
age: 18
})
</script>为 ref 和 reactive 提供类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import { ref, reactive } from 'vue'
interface User {
name: string;
age: number;
}
// 为 ref 提供类型
const user = ref<User>({ name: 'John', age: 30 })
// 为 reactive 提供类型
const state = reactive<{
count: number;
users: User[];
}>({
count: 0,
users: []
})使用 PropType 定义复杂 prop 类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import { defineComponent, PropType } from 'vue'
interface User {
name: string;
age: number;
}
export default defineComponent({
props: {
user: {
type: Object as PropType<User>,
required: true
},
callback: {
type: Function as PropType<(id: number) => void>,
required: true
}
}
})
14. Vue 3 中如何使用 Pinia 进行状态管理?
答案:
Pinia 是 Vue 官方推荐的状态管理库,用于替代 Vuex。使用方法如下:
安装 Pinia:
1
npm install pinia
创建 Pinia 实例并挂载到应用:
1
2
3
4
5
6
7
8
9import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')定义 Store:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31// stores/counter.js
import { defineStore } from 'pinia'
// 第一个参数是应用中 store 的唯一 ID
export const useCounterStore = defineStore('counter', {
// state
state: () => ({
count: 0,
name: 'Eduardo'
}),
// getters
getters: {
doubleCount: (state) => state.count * 2,
// 使用 this 访问其他 getter
doubleCountPlusOne() {
return this.doubleCount + 1
}
},
// actions
actions: {
increment() {
this.count++
},
async fetchData() {
const data = await api.get('...')
this.count = data.count
}
}
})使用 Setup Stores(更简洁的语法):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
// state
const count = ref(0)
const name = ref('Eduardo')
// getters
const doubleCount = computed(() => count.value * 2)
// actions
function increment() {
count.value++
}
return { count, name, doubleCount, increment }
})在组件中使用 Store:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<template>
<div>
<p>Count: {{ counter.count }}</p>
<p>Double count: {{ counter.doubleCount }}</p>
<button @click="counter.increment()">Increment</button>
<button @click="increment">Increment (extracted)</button>
</div>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'
const counter = useCounterStore()
// 解构 store 时保持响应性
const { count, doubleCount } = storeToRefs(counter)
// 直接解构 actions
const { increment } = counter
</script>修改 State:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 方式 1:直接修改
counter.count++
// 方式 2:使用 $patch 方法
counter.$patch({
count: counter.count + 1,
name: 'Alex'
})
// 方式 3:使用 $patch 函数
counter.$patch((state) => {
state.count++
state.name = 'Alex'
})
// 方式 4:使用 actions
counter.increment()重置 State:
1
counter.$reset()
15. Vue 3 中的 script setup 语法有什么优势?
答案:<script setup>
是 Vue 3.2 引入的编译时语法糖,它有以下优势:
更少的样板代码:
- 不需要返回要暴露给模板的变量
- 导入的组件自动注册,无需在 components 选项中声明
更好的性能:
- 编译时优化,减少运行时开销
- 模板中的变量访问不需要通过代理
更好的 TypeScript 支持:
- 直接在
<script setup>
中使用 TypeScript - 使用 defineProps 和 defineEmits 获得完整的类型推断
- 直接在
更好的 IDE 支持:
- 更好的自动补全
- 更准确的类型检查
1 | <script setup> |
使用 defineProps 和 defineEmits:
1 | <script setup> |
使用 TypeScript:
1 | <script setup lang="ts"> |
适合快速熟悉 Vue 3 的 GitHub 项目
以下是一些优质的 Vue 3 项目,可以帮助你快速熟悉 Vue 3 的各种特性和最佳实践:
Vue 3 官方示例:
- 链接:https://github.com/vuejs/vue-next-examples
- 描述:Vue 团队提供的 Vue 3 示例集合,涵盖了各种 API 的使用方法
Hoppscotch:
- 链接:https://github.com/hoppscotch/hoppscotch
- 描述:开源的 API 开发生态系统,使用 Vue 3 + TypeScript 构建,拥有 55K+ stars
VueUse:
- 链接:https://github.com/vueuse/vueuse
- 描述:Vue Composition API 的实用工具集合,包含大量可复用的组合式函数
Element Plus:
- 链接:https://github.com/element-plus/element-plus
- 描述:基于 Vue 3 的桌面端组件库
Vite:
- 链接:https://github.com/vitejs/vite
- 描述:下一代前端构建工具,由 Vue 作者尤雨溪创建,与 Vue 3 配合使用效果最佳
Vue 3 Instagram Clone:
- 链接:https://github.com/selemondev/8-Awesome-Vue-Projects
- 描述:使用 Vue 3、Pinia、TailwindCSS 和 Firebase 构建的 Instagram 克隆项目
Vue 3 Whatsapp Clone:
- 链接:https://github.com/selemondev/8-Awesome-Vue-Projects
- 描述:使用 Vue 3、Pinia、TailwindCSS 和 Firebase 构建的 Whatsapp 克隆项目
Vue 3 Netflix Clone:
- 链接:https://github.com/selemondev/8-Awesome-Vue-Projects
- 描述:使用 Vue 3、Supabase、Pinia 和 TailwindCSS 构建的 Netflix 克隆项目
Elk:
- 链接:https://github.com/elk-zone/elk
- 描述:一个 Mastodon 网页客户端,使用 Vue 3 和 Nuxt 3 构建
Pinia:
- 链接:https://github.com/vuejs/pinia
- 描述:Vue 官方推荐的状态管理库,了解其源码有助于深入理解 Vue 3 的响应式系统