常见们Vue3核心考点
OHNIIVue3核心考点
核心流程(记忆关键)
记忆口诀:响应式用Proxy,组合式API,性能更优,TypeScript友好
核心对比:Vue2 vs Vue3(面试必考)
第一部分:核心概念
1. Vue3 vs Vue2 核心区别
1.1 响应式原理
Vue2:Object.defineProperty
1 2 3 4 5 6 7 8 9
|
Object.defineProperty(obj, 'name', { get() { return value }, set(newVal) { value = newVal; notify() } })
|
Vue3:Proxy(推荐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
const proxy = new Proxy(obj, { get(target, key) { track(target, key) return target[key] }, set(target, key, value) { target[key] = value trigger(target, key) return true } })
|
1.2 API 风格
Vue2:Options API
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| export default { data() { return { count: 0 } }, methods: { increment() { this.count++ } }, computed: { double() { return this.count * 2 } }, mounted() { console.log('mounted') } }
|
Vue3:Composition API(推荐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { ref, computed, onMounted } from 'vue'
export default { setup() { const count = ref(0) const double = computed(() => count.value * 2) const increment = () => count.value++ onMounted(() => { console.log('mounted') }) return { count, double, increment } } }
|
1.3 核心对比表
| 特性 |
Vue2 |
Vue3 |
| 响应式 |
Object.defineProperty |
Proxy |
| API风格 |
Options API |
Composition API |
| TypeScript |
支持较弱 |
完美支持 |
| 性能 |
较慢 |
更快(编译优化) |
| 体积 |
较大 |
更小(Tree Shaking) |
| 生命周期 |
beforeCreate/created等 |
setup + onMounted等 |
| 多根节点 |
不支持 |
支持(Fragment) |
| Teleport |
不支持 |
支持 |
| Suspense |
不支持 |
支持 |
2. Composition API 核心
2.1 响应式 API
ref:基本类型响应式
1 2 3 4 5
| import { ref } from 'vue'
const count = ref(0) console.log(count.value) count.value++
|
reactive:对象响应式
1 2 3 4 5 6 7 8
| import { reactive } from 'vue'
const state = reactive({ count: 0, name: 'Vue3' }) console.log(state.count) state.count++
|
ref vs reactive
1 2 3 4 5 6 7 8 9 10 11
| const count = ref(0) count.value++
const state = reactive({ count: 0 }) state.count++
state = { count: 1 } state.count = 1
|
computed:计算属性
1 2 3 4 5 6 7 8 9 10 11 12
| import { ref, computed } from 'vue'
const count = ref(0) const double = computed(() => count.value * 2)
const fullName = computed({ get() { return firstName.value + ' ' + lastName.value }, set(value) { [firstName.value, lastName.value] = value.split(' ') } })
|
watch:侦听器
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
| import { ref, watch } from 'vue'
const count = ref(0)
watch(count, (newVal, oldVal) => { console.log(newVal, oldVal) })
watch([count, name], ([newCount, newName], [oldCount, oldName]) => { console.log(newCount, newName) })
watch(() => state.count, (newVal) => { console.log(newVal) })
watch(count, (newVal) => { console.log(newVal) }, { immediate: true })
watch(state, (newVal) => { console.log(newVal) }, { deep: true })
|
watchEffect:自动侦听
1 2 3 4 5 6 7 8
| import { ref, watchEffect } from 'vue'
const count = ref(0)
watchEffect(() => { console.log(count.value) })
|
2.2 生命周期钩子
Vue2 vs Vue3 生命周期对比
1 2 3 4 5 6 7 8 9 10 11 12
| beforeCreate → setup() created → setup() beforeMount → onBeforeMount mounted → onMounted beforeUpdate → onBeforeUpdate updated → onUpdated beforeDestroy → onBeforeUnmount destroyed → onUnmounted errorCaptured → onErrorCaptured onRenderTracked(调试用) onRenderTriggered(调试用)
|
使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { onMounted, onUpdated, onUnmounted } from 'vue'
export default { setup() { onMounted(() => { console.log('组件挂载') }) onUpdated(() => { console.log('组件更新') }) onUnmounted(() => { console.log('组件卸载') }) } }
|
3. Vue3 新特性
3.1 Teleport(传送门)
作用:将组件渲染到 DOM 的其他位置
1 2 3 4 5 6 7 8 9 10 11 12 13
| <template> <!-- 渲染到body下 --> <Teleport to="body"> <div class="modal"> <p>这是一个模态框</p> </div> </Teleport> <!-- 渲染到指定元素 --> <Teleport to="#modal-container"> <div>内容</div> </Teleport> </template>
|
使用场景:
- 模态框(Modal)
- 通知提示(Toast)
- 下拉菜单(Dropdown)
3.2 Suspense(异步组件)
作用:处理异步组件的加载状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <Suspense> <!-- 异步组件 --> <template #default> <AsyncComponent /> </template> <!-- 加载中显示 --> <template #fallback> <div>Loading...</div> </template> </Suspense> </template>
<script setup> // AsyncComponent.vue const data = await fetch('/api/data') // 可以直接使用await </script>
|
3.3 Fragment(多根节点)
Vue2:必须有单个根节点
1 2 3 4 5 6
| <template> <div> <!-- 必须有包裹元素 --> <h1>Title</h1> <p>Content</p> </div> </template>
|
Vue3:支持多根节点
1 2 3 4 5
| <template> <h1>Title</h1> <p>Content</p> <footer>Footer</footer> </template>
|
4. Proxy 响应式原理深入
4.1 Proxy 基础
什么是 Proxy?
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 32 33 34 35
|
const obj = { name: 'Vue3', age: 3 }
const proxy = new Proxy(obj, { get(target, key, receiver) { console.log(`读取 ${key}`) return Reflect.get(target, key, receiver) }, set(target, key, value, receiver) { console.log(`设置 ${key} = ${value}`) return Reflect.set(target, key, value, receiver) }, deleteProperty(target, key) { console.log(`删除 ${key}`) return Reflect.deleteProperty(target, key) }, has(target, key) { console.log(`检查 ${key}`) return Reflect.has(target, key) } })
proxy.name proxy.age = 4 delete proxy.age 'name' in proxy
|
Proxy vs Object.defineProperty
| 特性 |
Object.defineProperty |
Proxy |
| 监听对象属性新增 |
❌ 不支持 |
✅ 支持 |
| 监听对象属性删除 |
❌ 不支持 |
✅ 支持 |
| 监听数组索引变化 |
❌ 不支持 |
✅ 支持 |
| 监听数组 length |
❌ 不支持 |
✅ 支持 |
| 性能 |
需要递归遍历 |
懒代理,性能更好 |
| 兼容性 |
IE9+ |
不支持 IE |
4.2 Vue3 响应式实现原理
核心流程:响应式转换 → 依赖收集 → 派发更新
步骤1:响应式转换(reactive)
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| function reactive(target) { if (typeof target !== 'object' || target === null) { return target } const proxy = new Proxy(target, { get(target, key, receiver) { const result = Reflect.get(target, key, receiver) track(target, key) if (typeof result === 'object' && result !== null) { return reactive(result) } return result }, set(target, key, value, receiver) { const oldValue = target[key] const result = Reflect.set(target, key, value, receiver) if (oldValue !== value) { trigger(target, key) } return result }, deleteProperty(target, key) { const hadKey = Object.prototype.hasOwnProperty.call(target, key) const result = Reflect.deleteProperty(target, key) if (result && hadKey) { trigger(target, key) } return result } }) return proxy }
|
步骤2:依赖收集(track)
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 32 33 34 35 36 37 38
| let activeEffect = null
const targetMap = new WeakMap()
function track(target, key) { if (!activeEffect) return let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (!dep) { depsMap.set(key, (dep = new Set())) } dep.add(activeEffect) }
function effect(fn) { const effectFn = () => { activeEffect = effectFn fn() activeEffect = null } effectFn() return effectFn }
|
步骤3:派发更新(trigger)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function trigger(target, key) { const depsMap = targetMap.get(target) if (!depsMap) return const dep = depsMap.get(key) if (!dep) return dep.forEach(effect => { effect() }) }
|
完整示例:
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
| const state = reactive({ count: 0, user: { name: 'Vue3' } })
effect(() => { console.log('count:', state.count) })
state.count++
state.age = 3
delete state.age
state.user.name = 'Vue'
|
4.3 ref 的实现原理
ref 是对基本类型的响应式包装
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
| function ref(value) { const wrapper = { value } Object.defineProperty(wrapper, '__v_isRef', { value: true }) return reactive(wrapper) }
const count = ref(0) console.log(count.value)
effect(() => { console.log('count:', count.value) })
count.value++
|
4.4 响应式 API 对比
reactive vs ref
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const state = reactive({ count: 0, user: { name: 'Vue3' } }) state.count++
const count = ref(0) count.value++
const user = ref({ name: 'Vue3' }) user.value.name = 'Vue'
<template> <div>{{ count }}</div> <!-- 不需要 .value --> </template>
|
shallowReactive vs reactive
1 2 3 4 5 6 7 8 9 10 11 12
| const state = reactive({ user: { name: 'Vue3' } }) state.user.name = 'Vue'
const state = shallowReactive({ user: { name: 'Vue3' } }) state.user = { name: 'Vue' } state.user.name = 'React'
|
readonly vs reactive
1 2 3 4 5 6 7
| const state = reactive({ count: 0 }) state.count++
const readonlyState = readonly({ count: 0 }) readonlyState.count++
|
4.5 响应式陷阱与解决方案
陷阱1:reactive 解构丢失响应式
1 2 3 4 5 6 7 8 9 10 11 12
| const state = reactive({ count: 0 }) let { count } = state count++
import { toRefs } from 'vue' const { count } = toRefs(state) count.value++
state.count++
|
陷阱2:reactive 直接赋值丢失响应式
1 2 3 4 5 6 7 8 9 10
| let state = reactive({ count: 0 }) state = { count: 1 }
state.count = 1
let state = ref({ count: 0 }) state.value = { count: 1 }
|
陷阱3:数组响应式
1 2 3 4 5 6 7 8 9 10
| const arr = reactive([1, 2, 3])
arr[0] = 10 arr.length = 0 arr.push(4)
|
4.6 性能优化
1. 使用 shallowRef/shallowReactive
1 2 3 4 5 6 7 8 9 10
| const state = shallowReactive({ list: [] })
state.list = newList
state.list[0] = newItem
|
2. 使用 markRaw 标记非响应式
1 2 3 4 5 6 7 8
| import { reactive, markRaw } from 'vue'
const state = reactive({ chart: markRaw(new Chart()) })
|
3. 使用 triggerRef 手动触发更新
1 2 3 4 5 6 7 8 9
| import { shallowRef, triggerRef } from 'vue'
const state = shallowRef({ count: 0 })
state.value.count++
triggerRef(state)
|
第二部分:面试准备
4. 面试口述版本
4.1 Vue3 相比 Vue2 有哪些改进?(高频)
“Vue3 相比 Vue2 主要有以下改进:
第一,响应式原理升级。Vue2 使用 Object.defineProperty,无法监听数组索引和对象属性的新增删除。Vue3 使用 Proxy,可以监听所有操作,性能更好。
第二,Composition API。Vue2 的 Options API 在大型项目中逻辑分散,难以复用。Vue3 的 Composition API 可以按功能组织代码,逻辑更清晰,复用性更强。
第三,性能优化。Vue3 通过编译优化(静态提升、事件缓存)、Proxy 懒代理、Tree Shaking 等方式,性能提升约 1.5-2 倍,打包体积减少约 40%。
第四,TypeScript 支持。Vue3 使用 TypeScript 重写,类型推导更完善,开发体验更好。
第五,新特性。支持 Teleport 传送门、Suspense 异步组件、Fragment 多根节点等新特性。
总的来说,Vue3 在性能、开发体验、可维护性方面都有显著提升。”
4.2 Composition API 相比 Options API 有什么优势?(高频)
“Composition API 相比 Options API 主要有三个优势:
第一,逻辑组织更清晰。Options API 中,同一个功能的代码分散在 data、methods、computed 等不同选项中。Composition API 可以将相关逻辑组织在一起,代码更易读。
第二,逻辑复用更方便。Options API 使用 Mixins 复用逻辑,容易命名冲突,来源不清晰。Composition API 可以通过组合函数(Composables)复用逻辑,更灵活、更清晰。
第三,TypeScript 支持更好。Composition API 的类型推导更完善,IDE 提示更准确,开发体验更好。
不过 Options API 对初学者更友好,代码结构更固定。实际项目中可以根据团队情况选择,Vue3 两种 API 都支持。”
4.3 Vue3 响应式原理?(高频)
“Vue3 的响应式原理基于 Proxy,分为三个核心步骤:
第一,响应式转换。通过 reactive 或 ref 将数据转换为响应式对象。reactive 使用 Proxy 代理整个对象,ref 将值包装成对象并代理。
第二,依赖收集。当组件渲染时访问响应式数据,会触发 Proxy 的 get 拦截器,通过 track 函数收集当前组件的副作用函数(effect)。
第三,派发更新。当修改响应式数据时,触发 Proxy 的 set 拦截器,通过 trigger 函数找到依赖该数据的所有副作用函数,加入更新队列,异步批量执行。
相比 Vue2 的 Object.defineProperty,Proxy 可以监听对象属性的新增删除、数组索引变化,性能更好,且是懒代理,只有访问时才递归代理。”
4.4 详细说说 Proxy 响应式的实现过程?(深入)
“Proxy 响应式的实现分为三个核心部分:
第一,创建 Proxy 代理。使用 new Proxy 创建代理对象,拦截 get、set、deleteProperty 等操作。get 时收集依赖,set 时触发更新。
第二,依赖收集机制。使用 WeakMap 存储依赖关系,结构是 WeakMap<target, Map<key, Set>>。当访问属性时,将当前的副作用函数添加到对应的 Set 中。
第三,派发更新机制。当修改属性时,从 WeakMap 中找到对应的副作用函数集合,遍历执行。Vue3 会将更新放入队列,异步批量执行,避免重复渲染。
关键优化点:一是懒代理,只有访问嵌套对象时才递归代理,性能更好。二是使用 WeakMap,target 被回收时依赖关系自动清除,避免内存泄漏。三是批量更新,多次修改只触发一次渲染。”
4.5 ref 和 reactive 有什么区别?如何选择?(高频)
“ref 和 reactive 是 Vue3 中创建响应式数据的两种方式,主要区别有四点:
第一,适用类型不同。ref 适合基本类型(string、number、boolean),也可以包装对象。reactive 只适合对象和数组。
第二,访问方式不同。ref 需要通过 .value 访问和修改值,在模板中会自动解包。reactive 直接访问属性,不需要 .value。
第三,重新赋值的表现不同。ref 可以直接重新赋值,不会丢失响应式。reactive 直接赋值会丢失响应式,只能修改属性。
第四,实现原理不同。ref 本质是将值包装成对象 { value: xxx },然后用 reactive 代理。reactive 直接用 Proxy 代理整个对象。
选择建议:基本类型用 ref,对象可以用 reactive,但为了保持一致性,也可以统一用 ref。如果需要解构,用 ref 配合 toRefs 更方便。”
4.6 为什么 reactive 解构会丢失响应式?如何解决?(高频)
“reactive 解构丢失响应式的原因是:解构操作相当于取出了对象的属性值,得到的是一个普通变量,不再是 Proxy 代理对象,所以失去了响应式。
举个例子,const { count } = reactive({ count: 0 }),这里的 count 就是普通的数字 0,修改它不会触发更新。
解决方案有三种:
第一,使用 toRefs。toRefs 会将 reactive 对象的每个属性都转换为 ref,解构后仍然是响应式的。
第二,使用 toRef。如果只需要解构某个属性,可以用 toRef 单独转换。
第三,不解构,直接使用对象。这是最简单的方式,但在模板中需要写完整路径。
实际开发中,推荐使用 toRefs 配合解构,既保持响应式,又方便使用。”
5. 高频追问(7题)
Q1: ref 和 reactive 的区别?
ref:
- 适合基本类型(string、number、boolean)
- 访问和修改需要 .value
- 可以重新赋值
- 底层也是用 reactive 实现
reactive:
- 适合对象和数组
- 不需要 .value
- 不能直接重新赋值(会失去响应式)
- 底层使用 Proxy
选择建议:
- 基本类型用 ref
- 对象用 reactive
- 统一用 ref 也可以(更一致)
Q2: watch 和 watchEffect 的区别?
watch:
- 需要明确指定监听的数据源
- 可以访问新值和旧值
- 默认不立即执行(可配置 immediate)
- 适合需要对比新旧值的场景
watchEffect:
- 自动收集依赖,不需要指定数据源
- 无法访问旧值
- 立即执行一次
- 适合简单的副作用场景
1 2 3 4 5 6 7 8 9
| watch(count, (newVal, oldVal) => { console.log(newVal, oldVal) })
watchEffect(() => { console.log(count.value) })
|
Q3: Vue3 如何实现 Tree Shaking?
原理:
- Vue3 使用 ES Module 模块化
- 所有 API 都是具名导出(named export)
- 打包工具可以静态分析,删除未使用的代码
示例:
1 2 3 4
| import { ref, computed } from 'vue'
|
效果:
- Vue2 全量引入约 32KB
- Vue3 按需引入约 13KB(减少 60%)
Q4: Composition API 如何实现逻辑复用?
使用 Composables(组合函数):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { ref } from 'vue'
export function useCounter() { const count = ref(0) const increment = () => count.value++ const decrement = () => count.value-- return { count, increment, decrement } }
import { useCounter } from './useCounter'
export default { setup() { const { count, increment } = useCounter() return { count, increment } } }
|
优势:
- 逻辑清晰,易于理解
- 避免命名冲突
- 类型推导完善
- 可以组合多个 Composables
Q5: Vue3 的编译优化有哪些?
1. 静态提升(Static Hoisting)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <div> <p>静态文本</p> <p>{{ dynamic }}</p> </div>
const _hoisted_1 = createVNode("p", null, "静态文本")
function render() { return createVNode("div", null, [ _hoisted_1, createVNode("p", null, dynamic) ]) }
|
2. 事件缓存(Event Caching)
1 2 3 4 5 6
| <button @click="handleClick">Click</button>
const _cache = [] _cache[0] = handleClick
|
3. 静态标记(PatchFlag)
1 2
| createVNode("div", null, text, 1 )
|
Q6: setup 语法糖有什么优势?
传统 setup:
1 2 3 4 5 6 7 8 9 10
| <script> import { ref } from 'vue'
export default { setup() { const count = ref(0) return { count } // 需要手动返回 } } </script>
|
setup 语法糖:
1 2 3 4 5
| <script setup> import { ref } from 'vue'
const count = ref(0) // 自动暴露给模板 </script>
|
优势:
- 代码更简洁
- 自动暴露变量
- 更好的 TypeScript 支持
- 编译时优化
Q7: Vue3 如何处理异步组件?
方式1:defineAsyncComponent
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => import('./AsyncComponent.vue') )
const AsyncComp = defineAsyncComponent({ loader: () => import('./AsyncComponent.vue'), loadingComponent: LoadingComponent, errorComponent: ErrorComponent, delay: 200, timeout: 3000 })
|
方式2:Suspense
1 2 3 4 5 6 7 8
| <Suspense> <template #default> <AsyncComponent /> </template> <template #fallback> <Loading /> </template> </Suspense>
|
6. 实战经验(3个典型场景)
场景1:大型表单状态管理
问题: 表单字段多,Options API 代码分散
方案: 使用 Composition API 组织
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { reactive, computed } from 'vue'
export function useForm() { const form = reactive({ name: '', email: '', age: 0 }) const isValid = computed(() => { return form.name && form.email && form.age > 0 }) const submit = () => { if (isValid.value) { } } return { form, isValid, submit } }
|
结果: 逻辑清晰,易于维护
场景2:性能优化
问题: 大列表渲染卡顿
方案:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <template> <!-- 使用 v-memo 缓存 --> <div v-for="item in list" :key="item.id" v-memo="[item.id]"> {{ item.name }} </div> </template>
<script setup> import { shallowRef } from 'vue'
// 使用 shallowRef 避免深层响应式 const list = shallowRef([]) </script>
|
结果: 渲染性能提升 50%
场景3:逻辑复用
问题: 多个组件需要相同的鼠标位置追踪
方案: 提取 Composable
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
| import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() { const x = ref(0) const y = ref(0) const update = (e) => { x.value = e.pageX y.value = e.pageY } onMounted(() => { window.addEventListener('mousemove', update) }) onUnmounted(() => { window.removeEventListener('mousemove', update) }) return { x, y } }
const { x, y } = useMouse()
|
结果: 逻辑复用,代码简洁
第三部分:代码参考
7. 核心 API 速查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { ref, reactive, computed, watch, watchEffect } from 'vue'
const count = ref(0) const state = reactive({ name: 'Vue3' }) const double = computed(() => count.value * 2) watch(count, (newVal) => console.log(newVal)) watchEffect(() => console.log(count.value))
import { onMounted, onUpdated, onUnmounted } from 'vue'
onMounted(() => console.log('mounted')) onUpdated(() => console.log('updated')) onUnmounted(() => console.log('unmounted'))
import { toRef, toRefs, unref, isRef, isReactive } from 'vue'
const nameRef = toRef(state, 'name') const { name, age } = toRefs(state) const value = unref(count)
|
8. 记忆路线图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| Vue3 核心 ├── 响应式原理 │ ├── Proxy 代理 │ ├── 依赖收集(track) │ └── 派发更新(trigger) │ ├── Composition API │ ├── ref / reactive │ ├── computed / watch │ └── 生命周期钩子 │ ├── 新特性 │ ├── Teleport │ ├── Suspense │ └── Fragment │ └── 性能优化 ├── 编译优化 ├── Tree Shaking └── 响应式优化
|
9. 总结
核心要点:
- Vue3 使用 Proxy 实现响应式,性能更好
- Composition API 逻辑组织更清晰,复用更方便
- 编译优化、Tree Shaking 提升性能和体积
- TypeScript 支持更完善
- 新特性:Teleport、Suspense、Fragment
记忆口诀:
- Proxy 代理全监听,性能体积都优化
- Composition API 逻辑清,复用方便类型强
- 新特性多更灵活,开发体验大提升