一、基础概念题
Q1:Vue3 相比 Vue2 的主要改进有哪些?
参考答案:
1. 性能方面:
- 响应式系统重写(Proxy 替代 Object.defineProperty)
- 虚拟 DOM 优化(静态提升、Patch Flag、Block Tree)
- 编译优化(编译时标记、预字符串化)
- 初次渲染快 55%,更新快 133%,内存占用减少 54%
2. API 设计:
- Composition API(更好的逻辑复用和代码组织)
- 更好的 TypeScript 支持
- 支持 Tree-shaking(按需引入)
<script setup>语法糖
3. 新特性:
- Teleport 组件(传送门)
- Fragments(多根节点)
- Suspense(异步组件加载)
- 更好的自定义渲染器 API
4. 生态系统:
- Vite 构建工具(极速的开发体验)
- Pinia 状态管理(替代 Vuex)
- Vue Router 4 和 DevTools 3 的改进
Q2:为什么 Vue3 使用 Proxy 而不是 Object.defineProperty?
参考答案:
Object.defineProperty 的限制(Vue2):
javascript
// 1. 无法检测属性的添加和删除
const obj = { name: 'John' }
Object.defineProperty(obj, 'name', { /* ... */ })
obj.age = 25 // ❌ 不会触发响应式
delete obj.name // ❌ 不会触发响应式
// Vue2 需要使用 $set 和 $delete
this.$set(obj, 'age', 25)
this.$delete(obj, 'name')
// 2. 无法监听数组索引和长度
const arr = [1, 2, 3]
arr[0] = 100 // ❌ 不会触发响应式
arr.length = 0 // ❌ 不会触发响应式
// 3. 必须递归遍历对象的所有属性
// 初始化时性能开销大
Proxy 的优势(Vue3):
javascript
// 1. 可以检测属性的添加和删除
const state = reactive({ name: 'John' })
state.age = 25 // ✅ 自动响应式
delete state.name // ✅ 自动响应式
// 2. 可以监听数组索引和长度
const arr = reactive([1, 2, 3])
arr[0] = 100 // ✅ 自动响应式
arr.length = 0 // ✅ 自动响应式
// 3. 懒递归,按需代理
const state = reactive({
level1: {
level2: {
level3: {}
}
}
})
// 只有访问到 level2 时才会创建其 Proxy
// 4. 支持更多数据类型
const map = reactive(new Map())
const set = reactive(new Set())
性能对比:
| 特性 | Object.defineProperty | Proxy |
|---|---|---|
| 初始化性能 | 需要递归遍历所有属性 | 懒递归,按需处理 |
| 运行时性能 | getter/setter 调用 | 拦截器调用 |
| 内存占用 | 每个属性需要闭包 | 对象级别拦截 |
| 新增属性 | 不支持 | 支持 |
| 删除属性 | 不支持 | 支持 |
| 数组索引 | 不支持 | 支持 |
Q3:Composition API 解决了什么问题?
参考答案:
Options API 的问题(Vue2):
javascript
// 问题 1:逻辑分散
export default {
data() {
return {
// 用户相关
userName: '',
userAge: 0,
// 产品相关
productList: [],
productCount: 0
}
},
computed: {
// 用户相关计算属性在这里
userInfo() { /* ... */ },
// 产品相关计算属性在这里
totalProducts() { /* ... */ }
},
methods: {
// 用户相关方法在这里
fetchUser() { /* ... */ },
// 产品相关方法在这里
fetchProducts() { /* ... */ }
},
mounted() {
// 初始化逻辑混在一起
this.fetchUser()
this.fetchProducts()
}
}
// 一个功能的代码被强制分散到不同的选项中
// 维护时需要频繁上下跳转
javascript
// 问题 2:Mixins 的缺陷
// mixin1.js
export default {
data() {
return { count: 0 } // 命名冲突风险
},
methods: {
increment() { } // 命名冲突风险
}
}
// mixin2.js
export default {
data() {
return { count: 0 } // 同名属性!
}
}
// 组件
export default {
mixins: [mixin1, mixin2], // 哪个 count 会生效?
mounted() {
this.increment() // 这个方法从哪来的?不清楚!
}
}
// Mixins 的问题:
// 1. 命名冲突
// 2. 来源不清晰
// 3. 难以传递参数
// 4. 隐式依赖关系
Composition API 的解决方案:
javascript
// 解决方案 1:按功能组织代码
// composables/useUser.js
export function useUser() {
const userName = ref('')
const userAge = ref(0)
const userInfo = computed(() => `${userName.value} - ${userAge.value}`)
const fetchUser = async () => { /* ... */ }
return { userName, userAge, userInfo, fetchUser }
}
// composables/useProduct.js
export function useProduct() {
const productList = ref([])
const productCount = ref(0)
const totalProducts = computed(() => productList.value.length)
const fetchProducts = async () => { /* ... */ }
return { productList, productCount, totalProducts, fetchProducts }
}
// 组件中使用
export default {
setup() {
// 用户相关逻辑都在一起
const { userName, userAge, userInfo, fetchUser } = useUser()
// 产品相关逻辑都在一起
const { productList, totalProducts, fetchProducts } = useProduct()
onMounted(() => {
fetchUser()
fetchProducts()
})
return {
userName, userInfo,
productList, totalProducts
}
}
}
typescript
// 解决方案 2:更好的类型推导
// Vue2 + Options API
export default {
data() {
return {
count: 0 // 类型推导困难
}
},
computed: {
double() {
return this.count * 2 // this 的类型推导很困难
}
}
}
// Vue3 + Composition API
export default {
setup() {
const count = ref(0) // Ref<number>
const double = computed(() => count.value * 2) // ComputedRef<number>
// 完美的类型推导,无需手动标注
return { count, double }
}
}
优势总结:
| 特性 | Options API | Composition API |
|---|---|---|
| 逻辑组织 | 按选项类型 | 按功能 |
| 代码复用 | Mixins(有缺陷) | 组合函数(清晰) |
| TypeScript | 支持有限 | 完美支持 |
| 可测试性 | 需要组件实例 | 函数独立测试 |
| Tree-shaking | 不支持 | 支持 |
二、响应式系统题
Q4:ref 和 reactive 有什么区别?应该如何选择?
参考答案:
基本区别:
typescript
// ref - 适用于基本类型
const count = ref(0)
const message = ref('Hello')
const isActive = ref(false)
// 访问和修改需要 .value
console.log(count.value) // 0
count.value++ // 修改
// reactive - 适用于对象类型
const user = reactive({
name: 'John',
age: 25
})
// 直接访问和修改属性
console.log(user.name) // John
user.age++ // 修改
深层原理:
typescript
// ref 的实现(简化版)
function ref(value) {
return {
_isRef: true,
get value() {
track(this, 'value')
return value
},
set value(newValue) {
value = newValue
trigger(this, 'value')
}
}
}
// reactive 的实现(简化版)
function reactive(target) {
return new Proxy(target, {
get(target, key) {
track(target, key)
return Reflect.get(target, key)
},
set(target, key, value) {
const result = Reflect.set(target, key, value)
trigger(target, key)
return result
}
})
}
使用场景:
typescript
// ✅ 推荐使用 ref
const count = ref(0) // 基本类型
const user = ref<User | null>(null) // 可能为 null 的对象
const list = ref<Item[]>([]) // 整个数组需要替换
// ✅ 推荐使用 reactive
const form = reactive({ // 表单对象,属性多
username: '',
email: '',
password: ''
})
const state = reactive({ // 复杂的状态对象
user: { name: '', age: 0 },
settings: { theme: 'dark' }
})
常见陷阱:
typescript
// 陷阱 1:reactive 解构丢失响应式
const user = reactive({ name: 'John', age: 25 })
const { name, age } = user // ❌ 丢失响应式
// 解决方案:使用 toRefs
const { name, age } = toRefs(user) // ✅ 保持响应式
// 陷阱 2:reactive 整体替换
const state = reactive({ count: 0 })
state = reactive({ count: 1 }) // ❌ 错误!不能重新赋值
// 解决方案:使用 ref 或修改属性
const state = ref({ count: 0 })
state.value = { count: 1 } // ✅ 正确
// 或者
const state = reactive({ count: 0 })
state.count = 1 // ✅ 修改属性
// 陷阱 3:ref 在 reactive 中自动解包
const count = ref(0)
const state = reactive({
count // 自动解包
})
console.log(state.count) // 0,不需要 .value
state.count++ // ✅ 正确
// 但在数组中不会自动解包
const arr = reactive([ref(0)])
console.log(arr[0].value) // 需要 .value
选择建议:
typescript
// 1. 基本类型 → ref
const count = ref(0)
const message = ref('hello')
// 2. 对象类型 → reactive 或 ref
// 选择 reactive:
const user = reactive({ name: '', age: 0 })
// 选择 ref:
const user = ref({ name: '', age: 0 })
// reactive 的优势:不需要 .value
// ref 的优势:可以整体替换
// 3. 数组 → ref(因为经常需要整体替换)
const list = ref([])
list.value = newList // 常见操作
// 4. 在组合函数中统一返回 ref(推荐)
export function useCounter() {
const count = ref(0)
const double = computed(() => count.value * 2)
return { count, double } // 都是 ref 类型,使用一致
}
Q5:watchEffect 和 watch 的区别?
参考答案:
基本区别:
typescript
// watchEffect - 自动追踪依赖
const count = ref(0)
const message = ref('Hello')
watchEffect(() => {
// 自动追踪 count 和 message
console.log(`Count: ${count.value}, Message: ${message.value}`)
})
// 立即执行,并在 count 或 message 变化时重新执行
// watch - 显式指定监听源
watch(count, (newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`)
})
// 默认不立即执行,只在 count 变化时执行
详细对比:
typescript
// 1. 依赖追踪方式
// watchEffect - 自动
watchEffect(() => {
console.log(count.value) // 自动追踪 count
console.log(message.value) // 自动追踪 message
})
// watch - 手动指定
watch([count, message], ([newCount, newMessage]) => {
console.log(newCount, newMessage)
})
// 2. 是否立即执行
// watchEffect - 立即执行
watchEffect(() => {
console.log('立即执行一次')
})
// watch - 默认不立即执行
watch(count, () => {
console.log('只在 count 变化时执行')
})
// watch 可以配置立即执行
watch(count, () => {
console.log('立即执行一次,之后在 count 变化时执行')
}, { immediate: true })
// 3. 访问旧值
// watchEffect - 无法访问旧值
watchEffect(() => {
console.log(count.value) // 只能访问新值
})
// watch - 可以访问新值和旧值
watch(count, (newValue, oldValue) => {
console.log(`从 ${oldValue} 变为 ${newValue}`)
})
// 4. 监听多个源
// watchEffect - 自动追踪所有依赖
watchEffect(() => {
console.log(count.value, message.value, user.name)
// 自动追踪 count, message, user.name
})
// watch - 需要显式指定
watch([count, message, () => user.name], ([newCount, newMsg, newName]) => {
console.log(newCount, newMsg, newName)
})
使用场景:
typescript
// 场景 1:副作用(推荐 watchEffect)
// 示例:根据用户 ID 获取数据
const userId = ref(1)
watchEffect(async () => {
const response = await fetch(`/api/users/${userId.value}`)
const data = await response.json()
// 处理数据
})
// userId 变化时自动重新获取
// 场景 2:需要旧值(必须用 watch)
const count = ref(0)
watch(count, (newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`)
if (newValue > oldValue) {
console.log('Increased')
} else {
console.log('Decreased')
}
})
// 场景 3:惰性执行(推荐 watch)
const searchQuery = ref('')
// 不希望初始执行,只在搜索词变化时执行
watch(searchQuery, async (newQuery) => {
if (!newQuery) return
const results = await search(newQuery)
// 处理搜索结果
})
// 场景 4:监听深层对象(watch 配置 deep)
const user = reactive({
profile: {
name: 'John',
age: 25
}
})
watch(user, (newUser) => {
console.log('User changed')
}, { deep: true })
// user.profile.age++ 会触发回调
清理副作用:
typescript
// watchEffect - 使用 onCleanup
watchEffect((onCleanup) => {
const timer = setTimeout(() => {
console.log('Delayed log')
}, 1000)
// 在副作用重新执行或组件卸载前清理
onCleanup(() => {
clearTimeout(timer)
})
})
// watch - 第三个参数
watch(userId, async (newId, oldId, onCleanup) => {
let cancelled = false
onCleanup(() => {
cancelled = true
})
const data = await fetch(`/api/users/${newId}`)
if (!cancelled) {
// 使用数据
}
})
性能考虑:
typescript
// watchEffect 的性能陷阱
const bigData = reactive({
items: Array(10000).fill(0)
})
// ❌ 不好:会追踪所有 items
watchEffect(() => {
console.log('Items:', bigData.items)
})
// ✅ 更好:只追踪需要的部分
watchEffect(() => {
console.log('First item:', bigData.items[0])
})
// ✅ 或使用 watch 精确控制
watch(() => bigData.items[0], (newValue) => {
console.log('First item:', newValue)
})
选择指南:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 简单的副作用 | watchEffect | 代码简洁 |
| 需要旧值 | watch | watchEffect 不支持 |
| 惰性执行 | watch | 避免初始执行 |
| 监听特定源 | watch | 精确控制 |
| 异步操作 | watchEffect | 自动追踪依赖 |
三、组件通信题
Q6:Vue3 中父子组件通信有哪些方式?
参考答案:
1. Props 和 Emits(最常用)
vue
<!-- 父组件 -->
<template>
<ChildComponent
:message="parentMessage"
:count="count"
@update="handleUpdate"
@delete="handleDelete"
/>
</template>
<script setup>
const parentMessage = ref('Hello from parent')
const count = ref(0)
const handleUpdate = (newValue) => {
console.log('Updated:', newValue)
}
const handleDelete = (id) => {
console.log('Delete:', id)
}
</script>
<!-- 子组件 -->
<script setup lang="ts">
// 定义 props
interface Props {
message: string
count: number
}
const props = defineProps<Props>()
// 定义 emits
interface Emits {
(e: 'update', value: string): void
(e: 'delete', id: number): void
}
const emit = defineEmits<Emits>()
const handleClick = () => {
emit('update', 'New value')
emit('delete', 123)
}
</script>
2. v-model(双向绑定)
vue
<!-- 父组件 -->
<template>
<CustomInput v-model="searchText" />
<!-- 多个 v-model -->
<UserProfile
v-model:name="userName"
v-model:email="userEmail"
/>
</template>
<!-- CustomInput 子组件 -->
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<!-- UserProfile 子组件 -->
<script setup>
defineProps(['name', 'email'])
defineEmits(['update:name', 'update:email'])
</script>
<template>
<input :value="name" @input="$emit('update:name', $event.target.value)" />
<input :value="email" @input="$emit('update:email', $event.target.value)" />
</template>
3. Provide / Inject(跨层级通信)
typescript
// 祖先组件
import { provide, ref } from 'vue'
const theme = ref('dark')
const updateTheme = (newTheme: string) => {
theme.value = newTheme
}
// 提供数据和方法
provide('theme', theme)
provide('updateTheme', updateTheme)
// 或使用对象一次提供多个
provide('themeContext', {
theme,
updateTheme
})
// 后代组件(任意层级)
import { inject } from 'vue'
// 注入数据和方法
const theme = inject('theme')
const updateTheme = inject('updateTheme')
// 或注入整个上下文
const themeContext = inject('themeContext')
// 使用默认值
const theme = inject('theme', 'light') // 如果没有提供,使用 'light'
使用 InjectionKey 提供类型安全:
typescript
// keys.ts
import { InjectionKey, Ref } from 'vue'
interface ThemeContext {
theme: Ref<string>
updateTheme: (theme: string) => void
}
export const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme')
// 祖先组件
import { provide } from 'vue'
import { ThemeKey } from './keys'
const theme = ref('dark')
const updateTheme = (newTheme: string) => {
theme.value = newTheme
}
provide(ThemeKey, {
theme,
updateTheme
})
// 后代组件
import { inject } from 'vue'
import { ThemeKey } from './keys'
const themeContext = inject(ThemeKey)
// themeContext 有完整的类型提示!
4. Expose / Template Refs(父组件访问子组件)
vue
<!-- 子组件 -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
const message = ref('Hello')
const increment = () => {
count.value++
}
const reset = () => {
count.value = 0
}
// 暴露给父组件
defineExpose({
count,
increment,
reset
})
</script>
<!-- 父组件 -->
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const childRef = ref()
const handleClick = () => {
// 访问子组件暴露的属性和方法
console.log(childRef.value.count)
childRef.value.increment()
childRef.value.reset()
}
</script>
<template>
<ChildComponent ref="childRef" />
<button @click="handleClick">操作子组件</button>
</template>
5. Attrs(透传属性)
vue
<!-- 子组件 -->
<script setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
// attrs 包含所有未被 props 声明的属性
console.log(attrs.class)
console.log(attrs.onClick)
</script>
<template>
<!-- 自动继承父组件传递的属性 -->
<div v-bind="$attrs">
Content
</div>
<!-- 或手动控制 -->
<div>
<input v-bind="$attrs" />
</div>
</template>
<!-- 禁用自动继承 -->
<script setup>
defineOptions({
inheritAttrs: false
})
</script>
6. Pinia(全局状态管理)
typescript
// stores/user.ts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => {
const user = ref(null)
const login = async (credentials) => {
// 登录逻辑
}
return { user, login }
})
// 任意组件中使用
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
console.log(userStore.user)
userStore.login(credentials)
通信方式选择指南:
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 父 → 子 | Props | 单向数据流 |
| 子 → 父 | Emits | 事件通知 |
| 双向绑定 | v-model | 简化代码 |
| 跨层级 | Provide/Inject | 避免 prop drilling |
| 访问子组件实例 | Expose/Ref | 直接调用子组件方法 |
| 全局状态 | Pinia | 跨组件共享 |
| 兄弟组件 | Pinia 或共同父组件 | 状态管理或提升状态 |
四、生命周期题
Q7:Vue3 生命周期有哪些变化?
参考答案:
Options API 生命周期(与 Vue2 类似):
javascript
export default {
beforeCreate() {
// 实例初始化之后,数据观测和事件配置之前
},
created() {
// 实例创建完成,数据观测、属性和方法运算、watch/event 事件回调
// 还未挂载,$el 不可用
},
beforeMount() {
// 挂载开始之前,render 函数首次调用
},
mounted() {
// 实例挂载完成,可以访问 DOM
},
beforeUpdate() {
// 数据更新时,虚拟 DOM 打补丁之前
},
updated() {
// 虚拟 DOM 重新渲染和打补丁之后
},
beforeUnmount() { // Vue2 中叫 beforeDestroy
// 实例卸载之前
},
unmounted() { // Vue2 中叫 destroyed
// 实例卸载完成
}
}
Composition API 生命周期:
typescript
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
export default {
setup() {
// ⚠️ 没有 beforeCreate 和 created
// setup() 本身就相当于这两个钩子
console.log('相当于 beforeCreate 和 created')
onBeforeMount(() => {
console.log('before mount')
})
onMounted(() => {
console.log('mounted')
// 可以访问 DOM
})
onBeforeUpdate(() => {
console.log('before update')
})
onUpdated(() => {
console.log('updated')
})
onBeforeUnmount(() => {
console.log('before unmount')
// 清理工作:取消定时器、解绑事件等
})
onUnmounted(() => {
console.log('unmounted')
})
}
}
新增的生命周期钩子:
typescript
import {
onActivated,
onDeactivated,
onErrorCaptured,
onRenderTracked,
onRenderTriggered
} from 'vue'
// 1. Keep-alive 组件激活时
onActivated(() => {
console.log('Component activated')
})
// 2. Keep-alive 组件停用时
onDeactivated(() => {
console.log('Component deactivated')
})
// 3. 捕获子孙组件错误
onErrorCaptured((err, instance, info) => {
console.error('Error:', err)
console.log('Component:', instance)
console.log('Info:', info)
return false // 阻止错误继续传播
})
// 4. 开发环境:追踪响应式依赖
onRenderTracked((event) => {
console.log('Tracked:', event)
// 调试响应式依赖
})
// 5. 开发环境:响应式依赖触发重新渲染
onRenderTriggered((event) => {
console.log('Triggered:', event)
// 调试为什么重新渲染
})
生命周期执行顺序:
父组件 setup
父组件 onBeforeMount
子组件 setup
子组件 onBeforeMount
子组件 onMounted
父组件 onMounted
--- 更新时 ---
父组件 onBeforeUpdate
子组件 onBeforeUpdate
子组件 onUpdated
父组件 onUpdated
--- 卸载时 ---
父组件 onBeforeUnmount
子组件 onBeforeUnmount
子组件 onUnmounted
父组件 onUnmounted
实际使用示例:
typescript
export default {
setup() {
const data = ref(null)
const timer = ref(null)
// 相当于 created
console.log('组件创建')
onMounted(async () => {
// 获取数据
data.value = await fetchData()
// 启动定时器
timer.value = setInterval(() => {
console.log('Tick')
}, 1000)
// 添加事件监听
window.addEventListener('resize', handleResize)
})
onBeforeUnmount(() => {
// 清理定时器
if (timer.value) {
clearInterval(timer.value)
}
// 移除事件监听
window.removeEventListener('resize', handleResize)
})
return { data }
}
}
五、性能优化题
Q8:Vue3 中有哪些性能优化手段?
参考答案:
1. 编译时优化(自动)
vue
<!-- 静态提升 -->
<template>
<div>
<h1>{{ title }}</h1>
<p>Static content</p> <!-- 自动提升 -->
<span>{{ message }}</span>
</div>
</template>
<!-- 编译后 -->
<script>
const _hoisted_1 = createVNode('p', null, 'Static content')
export function render() {
return createVNode('div', null, [
createVNode('h1', null, _ctx.title),
_hoisted_1, // 复用
createVNode('span', null, _ctx.message)
])
}
</script>
2. v-memo 指令
vue
<template>
<!-- 大列表优化 -->
<div
v-for="item in list"
:key="item.id"
v-memo="[item.id, item.selected]"
>
<!-- 只有 id 或 selected 变化时才重新渲染 -->
<div>{{ item.name }}</div>
<div>{{ item.description }}</div>
</div>
</template>
3. 虚拟列表
typescript
// 使用 vue-virtual-scroller 或自己实现
import { ref, computed } from 'vue'
export function useVirtualList(items, itemHeight, visibleCount) {
const scrollTop = ref(0)
const visibleItems = computed(() => {
const start = Math.floor(scrollTop.value / itemHeight)
const end = start + visibleCount
return items.value.slice(start, end)
})
const totalHeight = computed(() => items.value.length * itemHeight)
const offsetY = computed(() =>
Math.floor(scrollTop.value / itemHeight) * itemHeight
)
return {
visibleItems,
totalHeight,
offsetY,
scrollTop
}
}
4. 异步组件
typescript
// 路由懒加载
const routes = [
{
path: '/dashboard',
component: () => import('@/views/Dashboard.vue')
}
]
// 组件懒加载
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent({
loader: () => import('./Heavy.vue'),
loadingComponent: LoadingSpinner,
delay: 200,
timeout: 3000
})
5. 使用 shallowRef 和 shallowReactive
typescript
// 对于大型数据结构
const bigData = shallowRef({
items: Array(10000).fill(0)
})
// 整体替换会触发更新
bigData.value = { items: newItems } // ✅
// 修改内部不触发更新(性能更好)
bigData.value.items[0] = 1 // ❌ 不触发更新
// 手动触发更新
import { triggerRef } from 'vue'
triggerRef(bigData)
6. computed 缓存
typescript
// ❌ 不好:每次都计算
const getTotal = () => {
return items.value.reduce((sum, item) => sum + item.price, 0)
}
// ✅ 好:缓存结果
const total = computed(() => {
return items.value.reduce((sum, item) => sum + item.price, 0)
})
7. 避免不必要的响应式
typescript
// ❌ 不好:配置对象不需要响应式
const config = reactive({
apiUrl: 'https://api.example.com'
})
// ✅ 好
const config = {
apiUrl: 'https://api.example.com'
}
// ❌ 不好:大型静态数据
const bigStaticData = reactive({
/* 10000+ 项 */
})
// ✅ 好:使用 markRaw
const bigStaticData = markRaw({
/* 10000+ 项 */
})
8. 使用 KeepAlive 缓存组件
vue
<template>
<KeepAlive :max="10">
<component :is="currentComponent" />
</KeepAlive>
</template>
性能优化检查清单:
markdown
- [ ] 使用 computed 代替 method
- [ ] 大列表使用虚拟滚动
- [ ] 使用 v-memo 优化列表
- [ ] 路由和组件懒加载
- [ ] 使用 shallowRef/shallowReactive
- [ ] 避免不必要的响应式
- [ ] 使用 KeepAlive 缓存组件
- [ ] 合理使用 v-show 和 v-if
- [ ] 使用 key 优化列表渲染
- [ ] 避免在模板中使用复杂表达式
总结: 这份文档涵盖了 Vue2 和 Vue3 的核心面试题,包括基础概念、响应式系统、组件通信、生命周期和性能优化等方面。掌握这些内容可以应对大部分 Vue 相关的面试问题。