目录
Vue 3 新特性
Q1: Vue 3 相比 Vue 2 有哪些重大改进?
详细解答:
Vue 3 带来了多个重大改进,主要包括:
1. 性能优化
- 虚拟 DOM 重写:优化了 diff 算法,性能提升 1.3-2 倍
- 静态提升:将静态节点提升到渲染函数外部,避免重复创建
- 事件监听缓存:cacheHandlers 优化事件处理
- SSR 性能提升:服务端渲染速度提升 2-3 倍
2. Composition API
- 更好的逻辑复用和代码组织
- TypeScript 支持更友好
- 解决了 Options API 的局限性
3. 更好的 TypeScript 支持
- 完全使用 TypeScript 重写
- 类型推导更准确
- IDE 支持更完善
4. 新增特性
- Fragment:支持多根节点
- Teleport:可以将组件渲染到 DOM 的其他位置
- Suspense:异步组件的加载状态处理
- Multiple v-model:支持多个 v-model 绑定
5. Tree-shaking 支持
- 按需引入,减小打包体积
- 未使用的功能可以被移除
代码示例:
javascript
// Vue 2 - Options API
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
},
mounted() {
console.log('Component mounted')
}
}
// Vue 3 - Composition API
import { ref, onMounted } from 'vue'
export default {
setup() {
const count = ref(0)
const increment = () => {
count.value++
}
onMounted(() => {
console.log('Component mounted')
})
return {
count,
increment
}
}
}
响应式系统
Q2: Vue 3 的响应式原理是什么?与 Vue 2 有什么区别?
详细解答:
Vue 3 响应式原理:
Vue 3 使用 Proxy 替代了 Vue 2 的 Object.defineProperty,实现了更强大的响应式系统。
核心机制:
- Proxy 拦截:拦截对象的读取、设置、删除等操作
- effect 函数:副作用函数,当依赖的响应式数据变化时自动执行
- track 函数:依赖收集,记录哪些 effect 依赖了哪些属性
- trigger 函数:派发更新,当属性变化时触发相关的 effect
与 Vue 2 的区别:
| 特性 | Vue 2 (Object.defineProperty) | Vue 3 (Proxy) |
|---|---|---|
| 新增属性 | 需要 Vue.set() |
自动响应 |
| 删除属性 | 需要 Vue.delete() |
自动响应 |
| 数组索引 | 无法直接监听 | 可以直接监听 |
| 数组长度 | 无法监听 | 可以监听 |
| Map/Set | 不支持 | 支持 |
| 性能 | 初始化时遍历所有属性 | 惰性处理,访问时才代理 |
代码实现示例:
javascript
// Vue 3 响应式核心简化实现
let activeEffect = null
function effect(fn) {
activeEffect = fn
fn()
activeEffect = null
}
function reactive(target) {
const handler = {
get(target, key, receiver) {
// 依赖收集
track(target, key)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
// 触发更新
trigger(target, key)
return result
}
}
return new Proxy(target, handler)
}
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 trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => effect())
}
}
// 使用示例
const state = reactive({ count: 0 })
effect(() => {
console.log('count is:', state.count)
})
state.count++ // 输出: count is: 1
优势总结:
- 更完整的拦截:可以拦截更多操作(in、delete、has 等)
- 更好的性能:不需要遍历所有属性进行劫持
- 更好的数组处理:直接监听数组变化
- 支持更多数据结构:Map、Set、WeakMap、WeakSet
组合式 API
Q3: 什么是 Composition API?相比 Options API 有什么优势?
详细解答:
Composition API 定义:
Composition API 是 Vue 3 引入的一套新的 API,通过 setup() 函数组织组件逻辑,提供了更灵活的代码组织方式。
核心概念:
- setup() 函数:组件的入口点,在组件创建之前执行
- 响应式 API:ref、reactive、computed、watch 等
- 生命周期钩子:onMounted、onUpdated 等
- 依赖注入:provide、inject
相比 Options API 的优势:
1. 更好的逻辑复用
javascript
// Options API - 逻辑分散
export default {
data() {
return {
// 用户相关
user: null,
// 产品相关
products: []
}
},
methods: {
// 用户相关
fetchUser() {},
// 产品相关
fetchProducts() {}
},
mounted() {
// 用户相关
this.fetchUser()
// 产品相关
this.fetchProducts()
}
}
// Composition API - 逻辑聚合
import { useUser } from './composables/useUser'
import { useProducts } from './composables/useProducts'
export default {
setup() {
// 用户相关逻辑完全聚合
const { user, fetchUser } = useUser()
// 产品相关逻辑完全聚合
const { products, fetchProducts } = useProducts()
return {
user,
fetchUser,
products,
fetchProducts
}
}
}
2. 可组合函数 (Composables)
javascript
// composables/useMouse.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return { x, y }
}
// 在组件中使用
import { useMouse } from './composables/useMouse'
export default {
setup() {
const { x, y } = useMouse()
return { x, y }
}
}
3. 更好的 TypeScript 支持
typescript
import { ref, Ref } from 'vue'
interface User {
id: number
name: string
email: string
}
export function useUser() {
const user: Ref<User | null> = ref(null)
const loading: Ref<boolean> = ref(false)
async function fetchUser(id: number): Promise<void> {
loading.value = true
try {
const response = await fetch(`/api/users/${id}`)
user.value = await response.json()
} finally {
loading.value = false
}
}
return {
user,
loading,
fetchUser
}
}
4. 更灵活的代码组织
javascript
// Options API - 按选项类型组织
export default {
data() {
return {
searchQuery: '',
searchResults: [],
isSearching: false,
sortBy: 'date',
filterType: 'all'
}
},
computed: {
filteredResults() {
// 复杂的过滤逻辑
},
sortedResults() {
// 复杂的排序逻辑
}
},
methods: {
async search() {},
updateFilter() {},
updateSort() {}
}
}
// Composition API - 按功能组织
import { ref, computed } from 'vue'
export default {
setup() {
// 搜索功能
const searchQuery = ref('')
const searchResults = ref([])
const isSearching = ref(false)
async function search() {
// 搜索逻辑
}
// 过滤功能
const filterType = ref('all')
const filteredResults = computed(() => {
// 过滤逻辑
})
function updateFilter(type) {
filterType.value = type
}
// 排序功能
const sortBy = ref('date')
const sortedResults = computed(() => {
// 排序逻辑
})
function updateSort(field) {
sortBy.value = field
}
return {
searchQuery,
searchResults,
isSearching,
search,
filterType,
filteredResults,
updateFilter,
sortBy,
sortedResults,
updateSort
}
}
}
5. 避免 this 指向问题
javascript
// Options API - this 指向可能混乱
export default {
methods: {
fetchData() {
setTimeout(function() {
// this 指向错误
this.data = []
}, 1000)
setTimeout(() => {
// 需要使用箭头函数
this.data = []
}, 1000)
}
}
}
// Composition API - 没有 this
import { ref } from 'vue'
export default {
setup() {
const data = ref([])
function fetchData() {
setTimeout(() => {
// 直接使用,无需担心 this
data.value = []
}, 1000)
}
return { data, fetchData }
}
}
总结:
Composition API 的核心优势是:
- 逻辑聚合:相关逻辑可以组织在一起
- 逻辑复用:通过 composables 轻松复用
- 类型推导:TypeScript 支持更好
- 代码组织:更灵活的组织方式
- tree-shaking:未使用的功能可以被移除
生命周期
Q4: Vue 3 的生命周期钩子有哪些?与 Vue 2 有什么区别?
详细解答:
Vue 3 Composition API 生命周期:
| Options API | Composition API | 执行时机 |
|---|---|---|
| beforeCreate | setup() | 组件实例创建之前 |
| created | setup() | 组件实例创建之后 |
| beforeMount | onBeforeMount | 挂载开始之前 |
| mounted | onMounted | 挂载完成后 |
| beforeUpdate | onBeforeUpdate | 数据更新,DOM 更新前 |
| updated | onUpdated | DOM 更新后 |
| beforeUnmount | onBeforeUnmount | 卸载之前 |
| unmounted | onUnmounted | 卸载完成后 |
| errorCaptured | onErrorCaptured | 捕获子组件错误 |
| renderTracked | onRenderTracked | 调试钩子,跟踪依赖 |
| renderTriggered | onRenderTriggered | 调试钩子,触发重新渲染 |
| activated | onActivated | keep-alive 组件激活 |
| deactivated | onDeactivated | keep-alive 组件停用 |
主要变化:
- beforeDestroy → beforeUnmount
- destroyed → unmounted
- setup() 替代了 beforeCreate 和 created
完整示例:
javascript
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onActivated,
onDeactivated,
onErrorCaptured,
onRenderTracked,
onRenderTriggered
} from 'vue'
export default {
setup() {
const count = ref(0)
console.log('setup - 相当于 beforeCreate 和 created')
onBeforeMount(() => {
console.log('onBeforeMount - DOM 挂载前')
})
onMounted(() => {
console.log('onMounted - DOM 挂载完成')
// 适合进行 DOM 操作、发送 API 请求
})
onBeforeUpdate(() => {
console.log('onBeforeUpdate - 数据更新,DOM 更新前')
})
onUpdated(() => {
console.log('onUpdated - DOM 更新完成')
// 注意:避免在这里修改状态,可能导致无限循环
})
onBeforeUnmount(() => {
console.log('onBeforeUnmount - 组件卸载前')
// 清理定时器、事件监听等
})
onUnmounted(() => {
console.log('onUnmounted - 组件已卸载')
})
// keep-alive 专用
onActivated(() => {
console.log('onActivated - 组件被激活')
})
onDeactivated(() => {
console.log('onDeactivated - 组件被停用')
})
// 错误捕获
onErrorCaptured((err, instance, info) => {
console.error('捕获到错误:', err, info)
return false // 返回 false 阻止错误继续传播
})
// 调试钩子
onRenderTracked((event) => {
console.log('依赖被跟踪:', event)
})
onRenderTriggered((event) => {
console.log('触发重新渲染:', event)
})
return {
count
}
}
}
实际应用场景:
javascript
import { ref, onMounted, onBeforeUnmount } from 'vue'
export default {
setup() {
const data = ref(null)
let timer = null
// 组件挂载后执行
onMounted(async () => {
// 1. 发送 API 请求
const response = await fetch('/api/data')
data.value = await response.json()
// 2. 添加事件监听
window.addEventListener('resize', handleResize)
// 3. 启动定时器
timer = setInterval(() => {
console.log('定时任务')
}, 1000)
// 4. 初始化第三方库
initThirdPartyLibrary()
})
// 组件卸载前清理
onBeforeUnmount(() => {
// 1. 移除事件监听
window.removeEventListener('resize', handleResize)
// 2. 清除定时器
if (timer) {
clearInterval(timer)
}
// 3. 销毁第三方库实例
destroyThirdPartyLibrary()
})
function handleResize() {
console.log('窗口大小改变')
}
return {
data
}
}
}
注意事项:
- setup() 中不能使用 this:因为 setup 执行时组件实例还未创建
- 生命周期钩子可以多次调用:同一个钩子可以注册多次
- onUpdated 避免修改状态:可能导致无限更新循环
- 清理副作用:在 onBeforeUnmount 中清理定时器、事件监听等
模板语法与指令
Q5: Vue 3 中的 Fragment、Teleport 和 Suspense 是什么?
详细解答:
1. Fragment (片段)
定义: Vue 3 支持多根节点,不再要求模板必须有单一根元素。
Vue 2 vs Vue 3:
vue
<!-- Vue 2 - 必须有单一根元素 -->
<template>
<div>
<header>Header</header>
<main>Content</main>
<footer>Footer</footer>
</div>
</template>
<!-- Vue 3 - 支持多根节点 -->
<template>
<header>Header</header>
<main>Content</main>
<footer>Footer</footer>
</template>
优势:
- 减少不必要的 DOM 嵌套
- 更清晰的组件结构
- 避免样式污染
2. Teleport (传送门)
定义: 将组件的 HTML 渲染到 DOM 的其他位置,而不影响组件的逻辑关系。
典型场景: Modal 弹窗、Toast 提示、Dropdown 下拉菜单
基本用法:
vue
<template>
<div class="container">
<button @click="showModal = true">打开弹窗</button>
<!-- 将 modal 渲染到 body 下 -->
<Teleport to="body">
<div v-if="showModal" class="modal">
<div class="modal-content">
<h2>这是一个弹窗</h2>
<button @click="showModal = false">关闭</button>
</div>
</div>
</Teleport>
</div>
</template>
<script setup>
import { ref } from 'vue'
const showModal = ref(false)
</script>
<style scoped>
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 8px;
}
</style>
高级用法 - 条件渲染目标:
vue
<template>
<Teleport :to="isMobile ? '#mobile-menu' : '#desktop-menu'" :disabled="!shouldTeleport">
<nav>
<a href="/">首页</a>
<a href="/about">关于</a>
</nav>
</Teleport>
</template>
<script setup>
import { ref, computed } from 'vue'
const isMobile = ref(window.innerWidth < 768)
const shouldTeleport = ref(true)
</script>
多个 Teleport 到同一目标:
vue
<!-- 组件 A -->
<Teleport to="#modals">
<div>Modal A</div>
</Teleport>
<!-- 组件 B -->
<Teleport to="#modals">
<div>Modal B</div>
</Teleport>
<!-- 渲染结果 -->
<div id="modals">
<div>Modal A</div>
<div>Modal B</div>
</div>
3. Suspense (悬念)
定义: 处理异步组件的加载状态,提供统一的加载和错误处理机制。
基本用法:
vue
<template>
<Suspense>
<!-- 异步组件 -->
<template #default>
<AsyncComponent />
</template>
<!-- 加载中状态 -->
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
)
</script>
异步组件示例:
vue
<!-- AsyncComponent.vue -->
<template>
<div>
<h1>{{ user.name }}</h1>
<p>{{ user.email }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
// 在 setup 中使用 await
const user = ref(null)
// 模拟异步数据获取
const response = await fetch('/api/user')
user.value = await response.json()
</script>
嵌套 Suspense:
vue
<template>
<Suspense>
<template #default>
<div>
<UserProfile />
<!-- 嵌套的 Suspense -->
<Suspense>
<template #default>
<UserPosts />
</template>
<template #fallback>
<div>加载文章中...</div>
</template>
</Suspense>
</div>
</template>
<template #fallback>
<div>加载用户信息中...</div>
</template>
</Suspense>
</template>
错误处理:
vue
<template>
<Suspense @pending="onPending" @resolve="onResolve" @fallback="onFallback">
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<LoadingSpinner />
</template>
</Suspense>
<!-- 使用 ErrorBoundary 捕获错误 -->
<div v-if="error" class="error">
{{ error.message }}
</div>
</template>
<script setup>
import { ref, onErrorCaptured } from 'vue'
const error = ref(null)
onErrorCaptured((err) => {
error.value = err
return false
})
function onPending() {
console.log('开始加载')
}
function onResolve() {
console.log('加载完成')
}
function onFallback() {
console.log('显示 fallback')
}
</script>
实际应用 - 完整示例:
vue
<!-- App.vue -->
<template>
<div id="app">
<Suspense>
<template #default>
<Dashboard />
</template>
<template #fallback>
<div class="loading-container">
<div class="spinner"></div>
<p>加载数据中...</p>
</div>
</template>
</Suspense>
<!-- Teleport 示例 -->
<Teleport to="body">
<NotificationContainer />
</Teleport>
</div>
</template>
<!-- Dashboard.vue -->
<script setup>
import { ref } from 'vue'
// 并行获取多个异步数据
const [userData, statsData, postsData] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/stats').then(r => r.json()),
fetch('/api/posts').then(r => r.json())
])
const user = ref(userData)
const stats = ref(statsData)
const posts = ref(postsData)
</script>
<template>
<div class="dashboard">
<h1>{{ user.name }}</h1>
<Stats :data="stats" />
<PostList :posts="posts" />
</div>
</template>
总结:
| 特性 | 用途 | 主要场景 |
|---|---|---|
| Fragment | 多根节点 | 减少不必要的包装元素 |
| Teleport | DOM 位置传送 | Modal、Toast、Dropdown |
| Suspense | 异步组件加载 | 数据获取、代码分割、懒加载 |