引言
Vue.js 作为一款渐进式 JavaScript 框架,自推出以来就以其简洁、灵活和高效的特性受到开发者的广泛喜爱。Vue 3 在 Vue 2 的基础上进行了全面的重构,引入了 Composition API、更好的 TypeScript 支持、性能优化等众多新特性,使其在大型项目和复杂场景中更具优势。本文将从 Vue 3 的核心概念入手,逐步深入到实战应用,并针对项目中常见的问题与挑战提供解决方案,帮助读者系统性地掌握 Vue 3 的核心技能。
一、Vue 3 基础入门
1.1 Vue 3 的新特性概览
Vue 3 相较于 Vue 2 主要带来了以下几大改进:
- 性能提升:通过静态标记和优化 diff 算法,渲染性能提升约 55%。
- Composition API:提供了更灵活的逻辑组织方式,特别适合大型项目。
- 更好的 TypeScript 支持:类型推断更加完善。
- 响应式系统重构:使用 Proxy 替代 Object.defineProperty,解决了 Vue 2 中响应式系统的诸多限制。
- Teleport 组件:允许将子组件渲染到 DOM 的其他位置。
- Fragment 片段:组件可以拥有多个根节点。
1.2 搭建 Vue 3 开发环境
推荐使用 Vite 作为构建工具,它提供了极速的开发体验。
# 使用 npm
npm create vite@latest my-vue-app -- --template vue
# 使用 yarn
yarn create vite my-vue-app --template vue
# 进入项目目录
cd my-vue-app
# 安装依赖
npm install
# 启动开发服务器
npm run dev
1.3 Vue 3 的基本语法
1.3.1 组件的创建
Vue 3 支持两种组件定义方式:Options API 和 Composition API。
Options API 示例:
<template>
<div>
<h1>{{ title }}</h1>
<button @click="increment">计数: {{ count }}</button>
</div>
</template>
<script>
export default {
data() {
return {
title: 'Vue 3 Options API 示例',
count: 0
}
},
methods: {
increment() {
this.count++
}
}
}
</script>
Composition API 示例:
<template>
<div>
<h1>{{ title }}</h1>
<button @click="increment">计数: {{ count }}</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const title = ref('Vue 3 Composition API 示例')
const count = ref(0)
function increment() {
count.value++
}
</script>
1.3.2 响应式系统
Vue 3 使用 ref 和 reactive 来创建响应式数据。
<script setup>
import { ref, reactive, computed } from 'vue'
// ref 用于基本类型和对象
const count = ref(0)
const user = ref({ name: '张三', age: 25 })
// reactive 用于对象
const state = reactive({
count: 0,
user: { name: '李四', age: 30 }
})
// 计算属性
const doubleCount = computed(() => count.value * 2)
const fullName = computed(() => `${user.value.name} (${user.value.age})`)
// 方法
function updateCount() {
count.value++
state.count++
}
</script>
二、Vue 3 核心技能深入
2.1 Composition API 详解
Composition API 是 Vue 3 最重要的特性之一,它允许我们按照逻辑功能来组织代码,而不是按照选项类型。
2.1.1 基本使用
<script setup>
import { ref, onMounted, watch } from 'vue'
// 响应式数据
const count = ref(0)
const message = ref('Hello Vue 3')
// 生命周期钩子
onMounted(() => {
console.log('组件已挂载')
})
// 监听器
watch(count, (newValue, oldValue) => {
console.log(`计数从 ${oldValue} 变为 ${newValue}`)
})
// 方法
function increment() {
count.value++
}
function updateMessage() {
message.value = 'Hello Composition API'
}
</script>
2.1.2 自定义组合函数
Composition API 的强大之处在于可以轻松创建可复用的逻辑组合。
示例:创建一个可复用的计数器组合函数
// composables/useCounter.js
import { ref, readonly } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
function increment() {
count.value++
}
function decrement() {
count.value--
}
function reset() {
count.value = initialValue
}
// 返回只读的 count,防止外部直接修改
return {
count: readonly(count),
increment,
decrement,
reset
}
}
在组件中使用:
<template>
<div>
<p>计数: {{ count }}</p>
<button @click="increment">增加</button>
<button @click="decrement">减少</button>
<button @click="reset">重置</button>
</div>
</template>
<script setup>
import { useCounter } from './composables/useCounter'
const { count, increment, decrement, reset } = useCounter(10)
</script>
2.2 响应式系统原理
Vue 3 的响应式系统基于 ES6 的 Proxy 实现,相比 Vue 2 的 Object.defineProperty 有以下优势:
- 可以监听对象属性的添加和删除
- 可以监听数组索引的变化
- 性能更好
2.2.1 响应式创建方式对比
import { ref, reactive, toRefs, toRef, isRef, isReactive } from 'vue'
// 1. ref - 用于基本类型和对象
const count = ref(0)
const user = ref({ name: '张三' })
// 2. reactive - 用于对象
const state = reactive({
count: 0,
user: { name: '李四' }
})
// 3. toRefs - 将 reactive 对象转换为 ref 对象集合
const stateRefs = toRefs(state)
// stateRefs.count 是一个 ref,stateRefs.user 也是一个 ref
// 4. toRef - 为 reactive 对象的某个属性创建 ref
const countRef = toRef(state, 'count')
// 5. 类型检查
console.log(isRef(count)) // true
console.log(isReactive(state)) // true
2.2.2 响应式数据的解构
import { reactive, toRefs } from 'vue'
const state = reactive({
count: 0,
user: {
name: '张三',
age: 25
}
})
// 错误做法:直接解构会失去响应式
const { count, user } = state
// count 和 user 不再是响应式的
// 正确做法:使用 toRefs
const { count, user } = toRefs(state)
// 现在 count 和 user 都是 ref,保持响应式
// 在模板中可以直接使用
// <div>{{ count }}</div>
// <div>{{ user.name }}</div>
2.3 组件通信与状态管理
2.3.1 Props 和 Emit
父组件向子组件传递数据:
<!-- Parent.vue -->
<template>
<ChildComponent
:title="parentTitle"
:count="parentCount"
@update-count="handleUpdateCount"
/>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const parentTitle = ref('来自父组件的标题')
const parentCount = ref(0)
function handleUpdateCount(newCount) {
parentCount.value = newCount
}
</script>
<!-- ChildComponent.vue -->
<template>
<div>
<h2>{{ title }}</h2>
<p>计数: {{ count }}</p>
<button @click="updateCount">更新计数</button>
</div>
</template>
<script setup>
// 定义 props
const props = defineProps({
title: {
type: String,
required: true
},
count: {
type: Number,
default: 0
}
})
// 定义 emit
const emit = defineEmits(['update-count'])
function updateCount() {
emit('update-count', props.count + 1)
}
</script>
2.3.2 Provide/Inject
适用于跨层级组件通信,特别适合插件和库的开发。
<!-- App.vue -->
<template>
<div>
<h1>Provide/Inject 示例</h1>
<ChildComponent />
</div>
</template>
<script setup>
import { provide, ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const theme = ref('dark')
const user = ref({ name: '管理员', role: 'admin' })
// 提供数据
provide('theme', theme)
provide('user', user)
</script>
<!-- ChildComponent.vue -->
<template>
<div>
<h2>子组件</h2>
<p>主题: {{ theme }}</p>
<p>用户: {{ user.name }} ({{ user.role }})</p>
<button @click="changeTheme">切换主题</button>
</div>
</template>
<script setup>
import { inject } from 'vue'
// 注入数据
const theme = inject('theme')
const user = inject('user')
function changeTheme() {
theme.value = theme.value === 'dark' ? 'light' : 'dark'
}
</script>
2.3.3 状态管理 - Pinia
Pinia 是 Vue 3 官方推荐的状态管理库,相比 Vuex 更轻量且支持 Composition API。
安装 Pinia:
npm install pinia
创建 Store:
// stores/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
// 状态
const users = ref([])
const currentUser = ref(null)
// 计算属性
const userCount = computed(() => users.value.length)
const isAdmin = computed(() => currentUser.value?.role === 'admin')
// Actions
function addUser(user) {
users.value.push(user)
}
function setCurrentUser(user) {
currentUser.value = user
}
function removeUser(userId) {
users.value = users.value.filter(u => u.id !== userId)
}
// 异步操作
async function fetchUsers() {
try {
const response = await fetch('/api/users')
const data = await response.json()
users.value = data
} catch (error) {
console.error('获取用户失败:', error)
}
}
return {
users,
currentUser,
userCount,
isAdmin,
addUser,
setCurrentUser,
removeUser,
fetchUsers
}
})
在组件中使用:
<template>
<div>
<h2>用户管理</h2>
<p>用户总数: {{ userStore.userCount }}</p>
<p>当前用户: {{ userStore.currentUser?.name || '未登录' }}</p>
<p v-if="userStore.isAdmin">您是管理员</p>
<button @click="addRandomUser">添加随机用户</button>
<button @click="fetchUsers">获取用户列表</button>
<ul>
<li v-for="user in userStore.users" :key="user.id">
{{ user.name }} - {{ user.role }}
<button @click="userStore.removeUser(user.id)">删除</button>
</li>
</ul>
</div>
</template>
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
function addRandomUser() {
const randomId = Math.floor(Math.random() * 1000)
userStore.addUser({
id: randomId,
name: `用户${randomId}`,
role: randomId % 2 === 0 ? 'admin' : 'user'
})
}
function fetchUsers() {
userStore.fetchUsers()
}
</script>
三、Vue 3 实战应用
3.1 路由管理 - Vue Router 4
Vue Router 4 是 Vue 3 官方路由库,支持 Composition API。
3.1.1 基本配置
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '@/views/HomeView.vue'
import AboutView from '@/views/AboutView.vue'
import UserView from '@/views/UserView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: AboutView
},
{
path: '/user/:id',
name: 'user',
component: UserView,
props: true // 将路由参数作为 props 传递
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import('@/views/DashboardView.vue'), // 懒加载
meta: { requiresAuth: true } // 路由元信息
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
// 导航守卫
router.beforeEach((to, from, next) => {
const isAuthenticated = localStorage.getItem('token')
if (to.meta.requiresAuth && !isAuthenticated) {
next('/login')
} else {
next()
}
})
export default router
3.1.2 在组件中使用路由
<template>
<div>
<nav>
<router-link to="/">首页</router-link>
<router-link to="/about">关于</router-link>
<router-link :to="{ name: 'user', params: { id: 123 } }">用户</router-link>
</nav>
<router-view />
</div>
</template>
<script setup>
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
// 获取路由参数
console.log(route.params.id)
// 编程式导航
function goToAbout() {
router.push('/about')
}
function goBack() {
router.back()
}
</script>
3.2 HTTP 请求 - Axios
3.2.1 封装 Axios 实例
// utils/request.js
import axios from 'axios'
import { useUserStore } from '@/stores/user'
// 创建 Axios 实例
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})
// 请求拦截器
service.interceptors.request.use(
config => {
const userStore = useUserStore()
// 添加认证 token
if (userStore.token) {
config.headers.Authorization = `Bearer ${userStore.token}`
}
// 添加请求时间戳
config.headers['X-Request-Time'] = Date.now()
return config
},
error => {
console.error('请求拦截器错误:', error)
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
response => {
const { data, status } = response
// 处理业务状态码
if (status === 200) {
return data
} else {
// 处理错误
return Promise.reject(new Error(data.message || '请求失败'))
}
},
error => {
// 处理 HTTP 错误
if (error.response) {
const { status, data } = error.response
switch (status) {
case 401:
console.error('未授权,请重新登录')
// 可以在这里处理跳转到登录页
break
case 403:
console.error('拒绝访问')
break
case 404:
console.error('请求资源不存在')
break
case 500:
console.error('服务器内部错误')
break
default:
console.error('其他错误:', error.message)
}
} else {
console.error('网络错误:', error.message)
}
return Promise.reject(error)
}
)
export default service
3.2.2 API 封装
// api/user.js
import request from '@/utils/request'
export function login(data) {
return request({
url: '/api/auth/login',
method: 'post',
data
})
}
export function getUserInfo() {
return request({
url: '/api/user/info',
method: 'get'
})
}
export function updateUser(data) {
return request({
url: '/api/user/update',
method: 'put',
data
})
}
3.3 表单处理与验证
3.3.1 使用 Vuelidate 进行表单验证
npm install @vuelidate/core @vuelidate/validators
<template>
<div>
<h2>用户注册表单</h2>
<form @submit.prevent="handleSubmit">
<div>
<label>用户名:</label>
<input
v-model="form.username"
@blur="v$.username.$touch()"
:class="{ 'error': v$.username.$error }"
/>
<div v-if="v$.username.$error" class="error-message">
<span v-for="error in v$.username.$errors" :key="error.$uid">
{{ error.$message }}
</span>
</div>
</div>
<div>
<label>邮箱:</label>
<input
v-model="form.email"
@blur="v$.email.$touch()"
:class="{ 'error': v$.email.$error }"
/>
<div v-if="v$.email.$error" class="error-message">
<span v-for="error in v$.email.$errors" :key="error.$uid">
{{ error.$message }}
</span>
</div>
</div>
<div>
<label>密码:</label>
<input
type="password"
v-model="form.password"
@blur="v$.password.$touch()"
:class="{ 'error': v$.password.$error }"
/>
<div v-if="v$.password.$error" class="error-message">
<span v-for="error in v$.password.$errors" :key="error.$uid">
{{ error.$message }}
</span>
</div>
</div>
<div>
<label>确认密码:</label>
<input
type="password"
v-model="form.confirmPassword"
@blur="v$.confirmPassword.$touch()"
:class="{ 'error': v$.confirmPassword.$error }"
/>
<div v-if="v$.confirmPassword.$error" class="error-message">
<span v-for="error in v$.confirmPassword.$errors" :key="error.$uid">
{{ error.$message }}
</span>
</div>
</div>
<button type="submit" :disabled="v$.$invalid">注册</button>
</form>
</div>
</template>
<script setup>
import { reactive, computed } from 'vue'
import { useVuelidate } from '@vuelidate/core'
import { required, email, minLength, sameAs } from '@vuelidate/validators'
const form = reactive({
username: '',
email: '',
password: '',
confirmPassword: ''
})
const rules = computed(() => ({
username: {
required,
minLength: minLength(3)
},
email: {
required,
email
},
password: {
required,
minLength: minLength(6)
},
confirmPassword: {
required,
sameAs: sameAs(form.password)
}
}))
const v$ = useVuelidate(rules, form)
function handleSubmit() {
v$.value.$touch()
if (!v$.value.$invalid) {
console.log('表单验证通过:', form)
// 提交表单数据
}
}
</script>
<style scoped>
.error {
border-color: red;
}
.error-message {
color: red;
font-size: 12px;
margin-top: 4px;
}
</style>
四、项目中常见问题与挑战的解决方案
4.1 性能优化问题
4.1.1 组件渲染优化
问题:大型列表渲染导致页面卡顿。
解决方案:使用虚拟滚动或分页。
<template>
<div>
<h2>虚拟滚动列表</h2>
<div class="list-container" ref="listContainer">
<div class="list-viewport" :style="viewportStyle">
<div
v-for="item in visibleItems"
:key="item.id"
class="list-item"
:style="getItemStyle(item)"
>
{{ item.name }} - {{ item.value }}
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
// 模拟大量数据
const allItems = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `项目 ${i}`,
value: Math.random() * 1000
}))
const itemHeight = 40 // 每项高度
const containerHeight = 400 // 容器高度
const visibleCount = Math.ceil(containerHeight / itemHeight) + 2 // 可见项数量
const scrollTop = ref(0)
const listContainer = ref(null)
// 计算可见项
const visibleItems = computed(() => {
const startIndex = Math.floor(scrollTop.value / itemHeight)
const endIndex = Math.min(startIndex + visibleCount, allItems.length)
return allItems.slice(startIndex, endIndex)
})
// 计算视口样式
const viewportStyle = computed(() => {
const totalHeight = allItems.length * itemHeight
const startIndex = Math.floor(scrollTop.value / itemHeight)
const offsetY = startIndex * itemHeight
return {
height: `${totalHeight}px`,
transform: `translateY(${offsetY}px)`
}
})
// 获取每项的样式
const getItemStyle = (item) => {
const index = allItems.indexOf(item)
return {
height: `${itemHeight}px`,
position: 'absolute',
top: `${index * itemHeight}px`,
width: '100%'
}
}
// 监听滚动事件
const handleScroll = () => {
if (listContainer.value) {
scrollTop.value = listContainer.value.scrollTop
}
}
onMounted(() => {
if (listContainer.value) {
listContainer.value.addEventListener('scroll', handleScroll)
}
})
onUnmounted(() => {
if (listContainer.value) {
listContainer.value.removeEventListener('scroll', handleScroll)
}
})
</script>
<style scoped>
.list-container {
height: 400px;
overflow-y: auto;
border: 1px solid #ccc;
position: relative;
}
.list-viewport {
position: relative;
}
.list-item {
border-bottom: 1px solid #eee;
padding: 0 10px;
display: flex;
align-items: center;
box-sizing: border-box;
}
</style>
4.1.2 代码分割与懒加载
问题:应用初始加载时间过长。
解决方案:使用动态导入进行代码分割。
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'home',
component: () => import('@/views/HomeView.vue')
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import('@/views/DashboardView.vue'),
meta: { requiresAuth: true }
},
{
path: '/admin',
name: 'admin',
component: () => import('@/views/AdminView.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
}
]
// 组件级别的懒加载
const router = createRouter({
history: createWebHistory(),
routes
})
// 路由级别的懒加载
router.beforeEach((to, from, next) => {
// 可以在这里处理路由级别的懒加载逻辑
next()
})
export default router
4.2 响应式数据管理问题
4.2.1 响应式数据丢失
问题:在异步操作或第三方库中,响应式数据可能丢失。
解决方案:使用 toRaw 和 markRaw 控制响应式。
import { reactive, toRaw, markRaw, watch } from 'vue'
const state = reactive({
user: null,
config: null
})
// 1. 使用 toRaw 获取原始对象(非响应式)
function updateUser(user) {
// 在某些情况下,我们可能需要原始对象
const rawUser = toRaw(user)
console.log('原始用户数据:', rawUser)
// 但更新时仍需使用响应式对象
state.user = user
}
// 2. 使用 markRaw 标记对象为非响应式
function loadConfig() {
// 如果配置数据不需要响应式,可以标记为非响应式
const config = markRaw({
apiVersion: '1.0',
features: ['feature1', 'feature2']
})
state.config = config
}
// 3. 监听响应式数据的变化
watch(
() => state.user,
(newUser, oldUser) => {
console.log('用户数据变化:', newUser)
// 这里可以执行一些副作用操作
},
{ deep: true }
)
4.2.2 大型对象响应式性能问题
问题:对大型对象进行深度响应式转换可能导致性能问题。
解决方案:使用 shallowRef 或 shallowReactive。
import { shallowRef, shallowReactive, watch } from 'vue'
// 1. 使用 shallowRef - 只有 .value 变化时才触发更新
const largeData = shallowRef({
// 假设这是一个包含大量数据的对象
items: Array.from({ length: 10000 }, (_, i) => ({
id: i,
data: `Item ${i}`
})),
metadata: {
total: 10000,
page: 1
}
})
// 2. 使用 shallowReactive - 只监听顶层属性变化
const state = shallowReactive({
user: { name: '张三', age: 25 },
settings: { theme: 'dark', language: 'zh' }
})
// 3. 性能优化示例
function updateLargeData() {
// 这种方式不会触发响应式更新
largeData.value.items.push({ id: 9999, data: 'New Item' })
// 需要手动触发更新
largeData.value = { ...largeData.value }
}
4.3 组件通信问题
4.3.1 跨层级组件通信
问题:深层嵌套组件之间的数据传递困难。
解决方案:使用 Provide/Inject 或事件总线。
方案一:Provide/Inject(推荐)
// composables/useEventBus.js
import { provide, inject, ref } from 'vue'
const EventBusSymbol = Symbol('event-bus')
export function createEventBus() {
const events = ref(new Map())
function on(event, callback) {
if (!events.value.has(event)) {
events.value.set(event, [])
}
events.value.get(event).push(callback)
}
function emit(event, ...args) {
const callbacks = events.value.get(event)
if (callbacks) {
callbacks.forEach(callback => callback(...args))
}
}
function off(event, callback) {
const callbacks = events.value.get(event)
if (callbacks) {
const index = callbacks.indexOf(callback)
if (index > -1) {
callbacks.splice(index, 1)
}
}
}
return { on, emit, off }
}
export function useEventBus() {
const eventBus = inject(EventBusSymbol)
if (!eventBus) {
throw new Error('EventBus not provided')
}
return eventBus
}
export function provideEventBus(app) {
const eventBus = createEventBus()
app.provide(EventBusSymbol, eventBus)
}
方案二:事件总线(简单场景)
// utils/eventBus.js
import { ref } from 'vue'
const listeners = ref(new Map())
export function on(event, callback) {
if (!listeners.value.has(event)) {
listeners.value.set(event, [])
}
listeners.value.get(event).push(callback)
}
export function emit(event, ...args) {
const callbacks = listeners.value.get(event)
if (callbacks) {
callbacks.forEach(callback => callback(...args))
}
}
export function off(event, callback) {
const callbacks = listeners.value.get(event)
if (callbacks) {
const index = callbacks.indexOf(callback)
if (index > -1) {
callbacks.splice(index, 1)
}
}
}
4.4 TypeScript 集成问题
4.4.1 类型定义不完整
问题:在 Vue 3 中使用 TypeScript 时,类型定义不完整。
解决方案:使用 defineProps 和 defineEmits 的泛型支持。
<script setup lang="ts">
import { ref, computed } from 'vue'
// 1. 使用泛型定义 Props
interface User {
id: number
name: string
email: string
role: 'admin' | 'user' | 'guest'
}
const props = defineProps<{
user: User
title: string
count?: number
}>()
// 2. 使用泛型定义 Emits
const emit = defineEmits<{
(e: 'update:user', user: User): void
(e: 'delete:user', userId: number): void
(e: 'click'): void
}>()
// 3. 响应式数据类型
const localUser = ref<User>(props.user)
const localCount = ref<number>(props.count || 0)
// 4. 计算属性类型
const displayName = computed<string>(() => {
return `${localUser.value.name} (${localUser.value.role})`
})
// 5. 方法类型
function updateUser(updatedUser: User): void {
emit('update:user', updatedUser)
}
function deleteUser(): void {
emit('delete:user', localUser.value.id)
}
function handleClick(): void {
emit('click')
}
</script>
4.4.2 组件实例类型
问题:在 Options API 中,this 的类型推断不准确。
解决方案:使用 defineComponent 和类型注解。
<script lang="ts">
import { defineComponent, ref, computed } from 'vue'
export default defineComponent({
name: 'UserCard',
props: {
user: {
type: Object as () => { id: number; name: string; email: string },
required: true
}
},
emits: {
'update:user': (user: { id: number; name: string; email: string }) => true
},
setup(props, { emit }) {
const localUser = ref(props.user)
const displayName = computed(() => {
return localUser.value.name
})
function updateUser() {
emit('update:user', localUser.value)
}
return {
localUser,
displayName,
updateUser
}
}
})
</script>
4.5 错误处理与调试
4.5.1 全局错误处理
问题:未捕获的错误可能导致应用崩溃。
解决方案:使用 Vue 3 的错误处理 API。
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
const app = createApp(App)
const pinia = createPinia()
// 全局错误处理
app.config.errorHandler = (err, instance, info) => {
console.error('Vue 错误:', err)
console.error('组件实例:', instance)
console.error('错误信息:', info)
// 可以发送到错误监控服务
// sendToErrorMonitoringService(err, instance, info)
// 可以显示错误提示
// showErrorNotification(err)
}
// 全局警告处理
app.config.warnHandler = (msg, instance, trace) => {
console.warn('Vue 警告:', msg)
console.warn('组件实例:', instance)
console.warn('调用栈:', trace)
}
// 全局性能标记
app.config.performance = true
app.use(pinia)
app.mount('#app')
4.5.2 组件内错误处理
<template>
<div>
<h2>错误处理示例</h2>
<button @click="triggerError">触发错误</button>
<div v-if="error" class="error-boundary">
<h3>发生错误</h3>
<p>{{ error.message }}</p>
<button @click="resetError">重试</button>
</div>
<div v-else>
<slot />
</div>
</div>
</template>
<script setup>
import { ref, onErrorCaptured } from 'vue'
const error = ref(null)
// 捕获子组件错误
onErrorCaptured((err, instance, info) => {
console.error('捕获到错误:', err)
error.value = err
return false // 阻止错误继续传播
})
function triggerError() {
throw new Error('这是一个测试错误')
}
function resetError() {
error.value = null
}
</script>
<style scoped>
.error-boundary {
border: 2px solid red;
padding: 20px;
background-color: #ffe6e6;
border-radius: 8px;
margin: 20px 0;
}
</style>
五、最佳实践与进阶技巧
5.1 项目结构组织
推荐的项目结构:
src/
├── assets/ # 静态资源
├── components/ # 通用组件
│ ├── common/ # 公共组件
│ └── layout/ # 布局组件
├── composables/ # 组合函数
├── stores/ # Pinia 状态管理
├── router/ # 路由配置
├── views/ # 页面组件
├── utils/ # 工具函数
├── services/ # API 服务
├── types/ # TypeScript 类型定义
├── App.vue # 根组件
└── main.ts # 入口文件
5.2 自定义指令
// directives/vPermission.js
export const vPermission = {
mounted(el, binding) {
const { value } = binding
const permissions = getPermissions() // 从 store 获取权限
if (!permissions.includes(value)) {
el.parentNode && el.parentNode.removeChild(el)
}
}
}
// directives/vLazyLoad.js
export const vLazyLoad = {
mounted(el, binding) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const src = binding.value
if (el.tagName === 'IMG') {
el.src = src
} else {
el.style.backgroundImage = `url(${src})`
}
observer.unobserve(el)
}
})
})
observer.observe(el)
}
}
5.3 插件开发
// plugins/myPlugin.js
export default {
install(app, options) {
// 1. 添加全局组件
app.component('MyGlobalComponent', MyGlobalComponent)
// 2. 添加全局指令
app.directive('my-directive', {
mounted(el, binding) {
el.style.color = binding.value || 'blue'
}
})
// 3. 添加全局方法
app.config.globalProperties.$myMethod = function() {
console.log('全局方法被调用')
}
// 4. 添加全局混入
app.mixin({
created() {
console.log('全局混入在组件创建时执行')
}
})
// 5. 提供/注入
app.provide('myPlugin', {
version: '1.0.0',
options
})
}
}
六、总结
掌握 Vue 3 需要系统性地学习其核心概念,并通过实战项目不断积累经验。本文从基础入门到实战应用,详细介绍了 Vue 3 的核心技能,并针对项目中常见的问题与挑战提供了具体的解决方案。
关键要点回顾:
- Composition API 是 Vue 3 的核心特性,提供了更灵活的代码组织方式
- 响应式系统 基于 Proxy 实现,性能更好且功能更强大
- Pinia 是 Vue 3 推荐的状态管理方案
- 性能优化 需要从多个角度考虑,包括代码分割、虚拟滚动等
- TypeScript 集成需要良好的类型定义和类型推断
- 错误处理 是保证应用稳定性的关键
进阶学习建议:
- 深入研究 Vue 3 源码,理解其响应式系统和编译原理
- 学习 Vue 3 的测试工具(Vitest、Vue Test Utils)
- 掌握 Vue 3 与后端框架(如 NestJS)的集成
- 学习 Vue 3 在移动端开发中的应用(如使用 Capacitor)
- 探索 Vue 3 在微前端架构中的应用
通过不断实践和总结,你将能够熟练运用 Vue 3 解决各种复杂的前端问题,构建高性能、可维护的 Web 应用。
