Skip to main content

Vue.js现代开发实践指南

Vue.js是一个渐进式JavaScript框架,以其简洁的API、优秀的性能和丰富的生态系统成为现代前端开发的热门选择。Vue 3引入的组合式API、更好的TypeScript支持和性能优化,使其成为构建现代Web应用的理想选择。

核心价值

Vue.js = 渐进式 + 响应式 + 组件化 + 生态丰富

  • 🎯 渐进式框架:可以逐步采用,从简单页面到复杂应用
  • 响应式系统:基于Proxy的高性能响应式数据绑定
  • 🧩 组件化开发:单文件组件,开发体验优秀
  • 🔧 组合式API:更好的逻辑复用和TypeScript支持
  • 📦 丰富生态:Vue Router、Pinia、Nuxt.js等完整解决方案
  • 🎨 开发体验:优秀的开发工具和调试支持

1. Vue 3核心特性与架构

1.1 Vue 3架构演进

Vue 3相比Vue 2在架构上有了重大改进,引入了组合式API、更好的性能和TypeScript支持。

Vue 2 vs Vue 3 对比

特性对比Vue 2Vue 3改进说明
响应式系统Object.definePropertyProxy更好的性能,支持数组和对象
API风格选项式API组合式API + 选项式API更好的逻辑复用和TypeScript支持
性能基准性能2x更快编译优化、Tree-shaking
包大小~34KB~16KB更好的Tree-shaking
TypeScript部分支持完全支持原生TypeScript支持
多根节点不支持支持Fragment支持

1.2 组合式API深度解析

组合式API是Vue 3的核心特性,提供了更灵活的逻辑组织方式和更好的TypeScript支持。

基础组合式API使用

组合式API基础示例
vue
1<template>
2 <div class="user-profile">
3 <!-- 用户信息展示 -->
4 <div class="user-info" v-if="!loading">
5 <img :src="user?.avatar" :alt="user?.name" class="avatar" />
6 <div class="details">
7 <h2>{{ user?.name }}</h2>
8 <p>{{ user?.email }}</p>
9 <span class="role">{{ user?.role }}</span>
10 </div>
11 </div>
12
13 <!-- 加载状态 -->
14 <div v-else class="loading">
15 <div class="spinner"></div>
16 <p>加载中...</p>
17 </div>
18
19 <!-- 错误状态 -->
20 <div v-if="error" class="error">
21 <p>{{ error }}</p>
22 <button @click="retry">重试</button>
23 </div>
24
25 <!-- 用户操作 -->
26 <div class="actions" v-if="user">
27 <button @click="toggleEdit" class="btn-primary">
28 {{ isEditing ? '取消编辑' : '编辑资料' }}
29 </button>
30 <button @click="logout" class="btn-secondary">登出</button>
31 </div>
32
33 <!-- 编辑表单 -->
34 <div v-if="isEditing" class="edit-form">
35 <form @submit.prevent="saveUser">
36 <div class="form-group">
37 <label for="name">姓名:</label>
38 <input
39 id="name"
40 v-model="editForm.name"
41 type="text"
42 required
43 class="form-input"
44 />
45 </div>
46
47 <div class="form-group">
48 <label for="email">邮箱:</label>
49 <input
50 id="email"
51 v-model="editForm.email"
52 type="email"
53 required
54 class="form-input"
55 />
56 </div>
57
58 <div class="form-actions">
59 <button type="submit" :disabled="saving" class="btn-primary">
60 {{ saving ? '保存中...' : '保存' }}
61 </button>
62 <button type="button" @click="cancelEdit" class="btn-secondary">
63 取消
64 </button>
65 </div>
66 </form>
67 </div>
68 </div>
69</template>
70
71<script setup lang="ts">
72import { ref, reactive, computed, onMounted, watch, nextTick } from 'vue'
73import { useRouter } from 'vue-router'
74import { useUserStore } from '@/stores/user'
75
76// 类型定义
77interface User {
78 id: number
79 name: string
80 email: string
81 avatar: string
82 role: 'admin' | 'user' | 'guest'
83}
84
85interface EditForm {
86 name: string
87 email: string
88}
89
90// 响应式数据
91const user = ref<User | null>(null)
92const loading = ref(true)
93const error = ref<string | null>(null)
94const isEditing = ref(false)
95const saving = ref(false)
96
97// 响应式对象
98const editForm = reactive<EditForm>({
99 name: '',
100 email: ''
101})
102
103// 组合式函数
104const router = useRouter()
105const userStore = useUserStore()
106
107// 计算属性
108const isAdmin = computed(() => user.value?.role === 'admin')
109const canEdit = computed(() => user.value && (isAdmin.value || user.value.id === userStore.currentUserId))
110
111// 生命周期钩子
112onMounted(async () => {
113 await fetchUser()
114})
115
116// 侦听器
117watch(user, (newUser) => {
118 if (newUser) {
119 editForm.name = newUser.name
120 editForm.email = newUser.email
121 }
122}, { immediate: true })
123
124// 方法定义
125const fetchUser = async () => {
126 try {
127 loading.value = true
128 error.value = null
129
130 const response = await fetch('/api/user/profile')
131 if (!response.ok) {
132 throw new Error('获取用户信息失败')
133 }
134
135 user.value = await response.json()
136 } catch (err) {
137 error.value = err instanceof Error ? err.message : '未知错误'
138 } finally {
139 loading.value = false
140 }
141}
142
143const toggleEdit = () => {
144 if (!canEdit.value) return
145
146 isEditing.value = !isEditing.value
147
148 if (isEditing.value) {
149 // 进入编辑模式时,聚焦到第一个输入框
150 nextTick(() => {
151 const firstInput = document.querySelector('.edit-form input') as HTMLInputElement
152 firstInput?.focus()
153 })
154 }
155}
156
157const saveUser = async () => {
158 if (!user.value) return
159
160 try {
161 saving.value = true
162
163 const response = await fetch(`/api/users/${user.value.id}`, {
164 method: 'PATCH',
165 headers: {
166 'Content-Type': 'application/json'
167 },
168 body: JSON.stringify(editForm)
169 })
170
171 if (!response.ok) {
172 throw new Error('保存失败')
173 }
174
175 const updatedUser = await response.json()
176 user.value = updatedUser
177 isEditing.value = false
178
179 // 显示成功消息
180 userStore.showMessage('保存成功', 'success')
181 } catch (err) {
182 error.value = err instanceof Error ? err.message : '保存失败'
183 } finally {
184 saving.value = false
185 }
186}
187
188const cancelEdit = () => {
189 if (user.value) {
190 editForm.name = user.value.name
191 editForm.email = user.value.email
192 }
193 isEditing.value = false
194}
195
196const retry = () => {
197 fetchUser()
198}
199
200const logout = async () => {
201 await userStore.logout()
202 router.push('/login')
203}
204</script>
205
206<style scoped>
207.user-profile {
208 max-width: 600px;
209 margin: 0 auto;
210 padding: 20px;
211}
212
213.user-info {
214 display: flex;
215 align-items: center;
216 gap: 20px;
217 padding: 20px;
218 border: 1px solid #e0e0e0;
219 border-radius: 8px;
220 background: #fff;
221}
222
223.avatar {
224 width: 80px;
225 height: 80px;
226 border-radius: 50%;
227 object-fit: cover;
228}
229
230.details h2 {
231 margin: 0 0 8px 0;
232 color: #333;
233}
234
235.details p {
236 margin: 0 0 8px 0;
237 color: #666;
238}
239
240.role {
241 display: inline-block;
242 padding: 4px 8px;
243 background: #e3f2fd;
244 color: #1976d2;
245 border-radius: 4px;
246 font-size: 12px;
247}
248
249.loading, .error {
250 text-align: center;
251 padding: 40px;
252}
253
254.spinner {
255 width: 40px;
256 height: 40px;
257 border: 4px solid #f3f3f3;
258 border-top: 4px solid #3498db;
259 border-radius: 50%;
260 animation: spin 1s linear infinite;
261 margin: 0 auto 16px;
262}
263
264@keyframes spin {
265 0% { transform: rotate(0deg); }
266 100% { transform: rotate(360deg); }
267}
268
269.actions {
270 display: flex;
271 gap: 12px;
272 margin-top: 20px;
273}
274
275.edit-form {
276 margin-top: 20px;
277 padding: 20px;
278 border: 1px solid #e0e0e0;
279 border-radius: 8px;
280 background: #f9f9f9;
281}
282
283.form-group {
284 margin-bottom: 16px;
285}
286
287.form-group label {
288 display: block;
289 margin-bottom: 4px;
290 font-weight: 500;
291}
292
293.form-input {
294 width: 100%;
295 padding: 8px 12px;
296 border: 1px solid #ddd;
297 border-radius: 4px;
298 font-size: 14px;
299}
300
301.form-input:focus {
302 outline: none;
303 border-color: #3498db;
304 box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
305}
306
307.form-actions {
308 display: flex;
309 gap: 12px;
310}
311
312.btn-primary, .btn-secondary {
313 padding: 8px 16px;
314 border: none;
315 border-radius: 4px;
316 cursor: pointer;
317 font-size: 14px;
318 transition: background-color 0.2s;
319}
320
321.btn-primary {
322 background: #3498db;
323 color: white;
324}
325
326.btn-primary:hover:not(:disabled) {
327 background: #2980b9;
328}
329
330.btn-primary:disabled {
331 background: #bdc3c7;
332 cursor: not-allowed;
333}
334
335.btn-secondary {
336 background: #95a5a6;
337 color: white;
338}
339
340.btn-secondary:hover {
341 background: #7f8c8d;
342}
343
344.error {
345 color: #e74c3c;
346}
347</style>

2. Vue组件化开发

2.1 单文件组件(SFC)架构

Vue的单文件组件提供了优秀的开发体验,将模板、逻辑和样式封装在一个文件中。

组件设计模式对比

设计模式适用场景优势劣势示例
展示组件UI渲染可复用、易测试功能单一Button, Card, Modal
容器组件业务逻辑逻辑集中耦合度高UserList, ProductManager
高阶组件功能增强横切关注点复杂度高withAuth, withLoading
Renderless组件逻辑复用灵活性高理解成本DataProvider, FormValidator

组件通信完整方案

父子组件通信示例
vue
1<!-- 父组件: UserManagement.vue -->
2<template>
3 <div class="user-management">
4 <div class="header">
5 <h1>用户管理</h1>
6 <button @click="showAddModal = true" class="btn-primary">
7 添加用户
8 </button>
9 </div>
10
11 <!-- 搜索和过滤 -->
12 <UserFilters
13 v-model:search="searchQuery"
14 v-model:role="selectedRole"
15 :roles="availableRoles"
16 @reset="resetFilters"
17 />
18
19 <!-- 用户列表 -->
20 <UserList
21 :users="filteredUsers"
22 :loading="loading"
23 @edit="handleEditUser"
24 @delete="handleDeleteUser"
25 @toggle-status="handleToggleStatus"
26 />
27
28 <!-- 添加/编辑用户模态框 -->
29 <UserModal
30 v-model:visible="showAddModal"
31 :user="editingUser"
32 :mode="modalMode"
33 @save="handleSaveUser"
34 @cancel="handleCancelEdit"
35 />
36
37 <!-- 确认删除对话框 -->
38 <ConfirmDialog
39 v-model:visible="showDeleteDialog"
40 title="确认删除"
41 :message="`确定要删除用户 ${deletingUser?.name} 吗?`"
42 @confirm="confirmDelete"
43 @cancel="showDeleteDialog = false"
44 />
45 </div>
46</template>
47
48<script setup lang="ts">
49import { ref, computed, onMounted } from 'vue'
50import UserFilters from './components/UserFilters.vue'
51import UserList from './components/UserList.vue'
52import UserModal from './components/UserModal.vue'
53import ConfirmDialog from './components/ConfirmDialog.vue'
54import { useUserApi } from '@/composables/useUserApi'
55import { useNotification } from '@/composables/useNotification'
56
57// 类型定义
58interface User {
59 id: number
60 name: string
61 email: string
62 role: 'admin' | 'user' | 'guest'
63 status: 'active' | 'inactive'
64 avatar?: string
65 createdAt: string
66}
67
68type ModalMode = 'add' | 'edit'
69
70// 响应式数据
71const searchQuery = ref('')
72const selectedRole = ref<string>('')
73const showAddModal = ref(false)
74const showDeleteDialog = ref(false)
75const editingUser = ref<User | null>(null)
76const deletingUser = ref<User | null>(null)
77const modalMode = ref<ModalMode>('add')
78
79// 组合式函数
80const { users, loading, fetchUsers, createUser, updateUser, deleteUser } = useUserApi()
81const { showSuccess, showError } = useNotification()
82
83// 计算属性
84const availableRoles = computed(() => [
85 { value: '', label: '全部角色' },
86 { value: 'admin', label: '管理员' },
87 { value: 'user', label: '普通用户' },
88 { value: 'guest', label: '访客' }
89])
90
91const filteredUsers = computed(() => {
92 return users.value.filter(user => {
93 const matchesSearch = !searchQuery.value ||
94 user.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
95 user.email.toLowerCase().includes(searchQuery.value.toLowerCase())
96
97 const matchesRole = !selectedRole.value || user.role === selectedRole.value
98
99 return matchesSearch && matchesRole
100 })
101})
102
103// 事件处理
104const handleEditUser = (user: User) => {
105 editingUser.value = { ...user }
106 modalMode.value = 'edit'
107 showAddModal.value = true
108}
109
110const handleDeleteUser = (user: User) => {
111 deletingUser.value = user
112 showDeleteDialog.value = true
113}
114
115const handleToggleStatus = async (user: User) => {
116 try {
117 const newStatus = user.status === 'active' ? 'inactive' : 'active'
118 await updateUser(user.id, { status: newStatus })
119 showSuccess(`用户状态已更新为${newStatus === 'active' ? '激活' : '禁用'}`)
120 } catch (error) {
121 showError('更新用户状态失败')
122 }
123}
124
125const handleSaveUser = async (userData: Partial<User>) => {
126 try {
127 if (modalMode.value === 'add') {
128 await createUser(userData)
129 showSuccess('用户创建成功')
130 } else {
131 await updateUser(editingUser.value!.id, userData)
132 showSuccess('用户更新成功')
133 }
134
135 showAddModal.value = false
136 editingUser.value = null
137 } catch (error) {
138 showError(modalMode.value === 'add' ? '创建用户失败' : '更新用户失败')
139 }
140}
141
142const handleCancelEdit = () => {
143 showAddModal.value = false
144 editingUser.value = null
145}
146
147const confirmDelete = async () => {
148 if (!deletingUser.value) return
149
150 try {
151 await deleteUser(deletingUser.value.id)
152 showSuccess('用户删除成功')
153 showDeleteDialog.value = false
154 deletingUser.value = null
155 } catch (error) {
156 showError('删除用户失败')
157 }
158}
159
160const resetFilters = () => {
161 searchQuery.value = ''
162 selectedRole.value = ''
163}
164
165// 生命周期
166onMounted(() => {
167 fetchUsers()
168})
169</script>
170
171<!-- 子组件: UserFilters.vue -->
172<template>
173 <div class="user-filters">
174 <div class="filter-group">
175 <label for="search">搜索用户:</label>
176 <input
177 id="search"
178 :value="search"
179 @input="$emit('update:search', $event.target.value)"
180 type="text"
181 placeholder="输入姓名或邮箱..."
182 class="search-input"
183 />
184 </div>
185
186 <div class="filter-group">
187 <label for="role">角色筛选:</label>
188 <select
189 id="role"
190 :value="role"
191 @change="$emit('update:role', $event.target.value)"
192 class="role-select"
193 >
194 <option
195 v-for="roleOption in roles"
196 :key="roleOption.value"
197 :value="roleOption.value"
198 >
199 {{ roleOption.label }}
200 </option>
201 </select>
202 </div>
203
204 <button @click="$emit('reset')" class="btn-secondary">
205 重置筛选
206 </button>
207 </div>
208</template>
209
210<script setup lang="ts">
211// Props定义
212interface Props {
213 search: string
214 role: string
215 roles: Array<{ value: string; label: string }>
216}
217
218defineProps<Props>()
219
220// 事件定义
221interface Emits {
222 'update:search': [value: string]
223 'update:role': [value: string]
224 'reset': []
225}
226
227defineEmits<Emits>()
228</script>
229
230<!-- 子组件: UserList.vue -->
231<template>
232 <div class="user-list">
233 <div v-if="loading" class="loading">
234 <div class="spinner"></div>
235 <p>加载中...</p>
236 </div>
237
238 <div v-else-if="users.length === 0" class="empty-state">
239 <p>暂无用户数据</p>
240 </div>
241
242 <div v-else class="user-grid">
243 <div
244 v-for="user in users"
245 :key="user.id"
246 class="user-card"
247 :class="{ 'user-card--inactive': user.status === 'inactive' }"
248 >
249 <div class="user-avatar">
250 <img
251 :src="user.avatar || '/default-avatar.png'"
252 :alt="user.name"
253 class="avatar-image"
254 />
255 <div class="status-indicator" :class="`status--${user.status}`"></div>
256 </div>
257
258 <div class="user-info">
259 <h3 class="user-name">{{ user.name }}</h3>
260 <p class="user-email">{{ user.email }}</p>
261 <span class="user-role" :class="`role--${user.role}`">
262 {{ getRoleLabel(user.role) }}
263 </span>
264 </div>
265
266 <div class="user-actions">
267 <button
268 @click="$emit('edit', user)"
269 class="btn-icon"
270 title="编辑"
271 >
272 ✏️
273 </button>
274 <button
275 @click="$emit('toggle-status', user)"
276 class="btn-icon"
277 :title="user.status === 'active' ? '禁用' : '启用'"
278 >
279 {{ user.status === 'active' ? '🔒' : '🔓' }}
280 </button>
281 <button
282 @click="$emit('delete', user)"
283 class="btn-icon btn-danger"
284 title="删除"
285 >
286 🗑️
287 </button>
288 </div>
289 </div>
290 </div>
291 </div>
292</template>
293
294<script setup lang="ts">
295// Props和Emits定义
296interface User {
297 id: number
298 name: string
299 email: string
300 role: 'admin' | 'user' | 'guest'
301 status: 'active' | 'inactive'
302 avatar?: string
303}
304
305interface Props {
306 users: User[]
307 loading: boolean
308}
309
310interface Emits {
311 'edit': [user: User]
312 'delete': [user: User]
313 'toggle-status': [user: User]
314}
315
316defineProps<Props>()
317defineEmits<Emits>()
318
319// 工具函数
320const getRoleLabel = (role: string) => {
321 const roleLabels = {
322 admin: '管理员',
323 user: '普通用户',
324 guest: '访客'
325 }
326 return roleLabels[role] || role
327}
328</script>

3. Vue生态系统与工具链

3.1 Vue Router 4路由管理

Vue Router 4为Vue 3应用提供了强大的路由功能,支持嵌套路由、路由守卫、动态路由等特性。

路由配置最佳实践

路由特性使用场景配置方式性能影响
嵌套路由多层级页面children配置中等
动态路由参数化页面:id语法
懒加载代码分割import()函数优化首屏
路由守卫权限控制beforeEnter等
命名路由编程导航name属性

完整路由配置示例

Vue Router完整配置
typescript
1// router/index.ts
2import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
3import { useUserStore } from '@/stores/user'
4import { useNotificationStore } from '@/stores/notification'
5
6// 路由类型定义
7declare module 'vue-router' {
8 interface RouteMeta {
9 title?: string
10 requiresAuth?: boolean
11 roles?: string[]
12 permissions?: string[]
13 layout?: string
14 keepAlive?: boolean
15 showInMenu?: boolean
16 icon?: string
17 }
18}
19
20// 路由配置
21const routes: RouteRecordRaw[] = [
22 {
23 path: '/',
24 name: 'Home',
25 component: () => import('@/views/Home.vue'),
26 meta: {
27 title: '首页',
28 showInMenu: true,
29 icon: '🏠'
30 }
31 },
32
33 // 认证相关路由
34 {
35 path: '/auth',
36 component: () => import('@/layouts/AuthLayout.vue'),
37 children: [
38 {
39 path: 'login',
40 name: 'Login',
41 component: () => import('@/views/auth/Login.vue'),
42 meta: {
43 title: '登录',
44 layout: 'auth'
45 }
46 },
47 {
48 path: 'register',
49 name: 'Register',
50 component: () => import('@/views/auth/Register.vue'),
51 meta: {
52 title: '注册',
53 layout: 'auth'
54 }
55 },
56 {
57 path: 'forgot-password',
58 name: 'ForgotPassword',
59 component: () => import('@/views/auth/ForgotPassword.vue'),
60 meta: {
61 title: '忘记密码',
62 layout: 'auth'
63 }
64 }
65 ]
66 },
67
68 // 用户相关路由
69 {
70 path: '/user',
71 component: () => import('@/layouts/DefaultLayout.vue'),
72 meta: {
73 requiresAuth: true
74 },
75 children: [
76 {
77 path: 'profile',
78 name: 'UserProfile',
79 component: () => import('@/views/user/Profile.vue'),
80 meta: {
81 title: '个人资料',
82 requiresAuth: true,
83 showInMenu: true,
84 icon: '👤'
85 }
86 },
87 {
88 path: 'settings',
89 name: 'UserSettings',
90 component: () => import('@/views/user/Settings.vue'),
91 meta: {
92 title: '用户设置',
93 requiresAuth: true,
94 showInMenu: true,
95 icon: '⚙️'
96 }
97 },
98 {
99 path: 'notifications',
100 name: 'UserNotifications',
101 component: () => import('@/views/user/Notifications.vue'),
102 meta: {
103 title: '消息通知',
104 requiresAuth: true,
105 showInMenu: true,
106 icon: '🔔'
107 }
108 }
109 ]
110 },
111
112 // 管理员路由
113 {
114 path: '/admin',
115 component: () => import('@/layouts/AdminLayout.vue'),
116 meta: {
117 requiresAuth: true,
118 roles: ['admin']
119 },
120 children: [
121 {
122 path: '',
123 name: 'AdminDashboard',
124 component: () => import('@/views/admin/Dashboard.vue'),
125 meta: {
126 title: '管理面板',
127 requiresAuth: true,
128 roles: ['admin'],
129 showInMenu: true,
130 icon: '📊'
131 }
132 },
133 {
134 path: 'users',
135 name: 'AdminUsers',
136 component: () => import('@/views/admin/Users.vue'),
137 meta: {
138 title: '用户管理',
139 requiresAuth: true,
140 roles: ['admin'],
141 permissions: ['users.read'],
142 showInMenu: true,
143 icon: '👥'
144 }
145 },
146 {
147 path: 'users/:id',
148 name: 'AdminUserDetail',
149 component: () => import('@/views/admin/UserDetail.vue'),
150 meta: {
151 title: '用户详情',
152 requiresAuth: true,
153 roles: ['admin'],
154 permissions: ['users.read']
155 },
156 props: true
157 },
158 {
159 path: 'settings',
160 name: 'AdminSettings',
161 component: () => import('@/views/admin/Settings.vue'),
162 meta: {
163 title: '系统设置',
164 requiresAuth: true,
165 roles: ['admin'],
166 permissions: ['settings.manage'],
167 showInMenu: true,
168 icon: '🔧'
169 }
170 }
171 ]
172 },
173
174 // 动态路由示例
175 {
176 path: '/posts/:id(\\d+)',
177 name: 'PostDetail',
178 component: () => import('@/views/posts/PostDetail.vue'),
179 meta: {
180 title: '文章详情'
181 },
182 props: route => ({
183 id: Number(route.params.id),
184 tab: route.query.tab
185 })
186 },
187
188 // 可选参数路由
189 {
190 path: '/search/:keyword?',
191 name: 'Search',
192 component: () => import('@/views/Search.vue'),
193 meta: {
194 title: '搜索'
195 }
196 },
197
198 // 通配符路由
199 {
200 path: '/docs/:path(.*)*',
201 name: 'Documentation',
202 component: () => import('@/views/Documentation.vue'),
203 meta: {
204 title: '文档'
205 }
206 },
207
208 // 重定向
209 {
210 path: '/dashboard',
211 redirect: { name: 'Home' }
212 },
213
214 // 404页面
215 {
216 path: '/:pathMatch(.*)*',
217 name: 'NotFound',
218 component: () => import('@/views/errors/NotFound.vue'),
219 meta: {
220 title: '页面未找到'
221 }
222 }
223]
224
225// 创建路由实例
226const router = createRouter({
227 history: createWebHistory(import.meta.env.BASE_URL),
228 routes,
229 scrollBehavior(to, from, savedPosition) {
230 // 滚动行为
231 if (savedPosition) {
232 return savedPosition
233 } else if (to.hash) {
234 return { el: to.hash, behavior: 'smooth' }
235 } else {
236 return { top: 0 }
237 }
238 }
239})
240
241// 全局前置守卫
242router.beforeEach(async (to, from, next) => {
243 const userStore = useUserStore()
244 const notificationStore = useNotificationStore()
245
246 // 设置页面标题
247 if (to.meta.title) {
248 document.title = `${to.meta.title} - My App`
249 }
250
251 // 检查认证要求
252 if (to.meta.requiresAuth) {
253 if (!userStore.isAuthenticated) {
254 notificationStore.showError('请先登录')
255 next({
256 name: 'Login',
257 query: { redirect: to.fullPath }
258 })
259 return
260 }
261
262 // 检查角色权限
263 if (to.meta.roles && !userStore.hasAnyRole(to.meta.roles)) {
264 notificationStore.showError('权限不足')
265 next({ name: 'Home' })
266 return
267 }
268
269 // 检查具体权限
270 if (to.meta.permissions && !userStore.hasAnyPermission(to.meta.permissions)) {
271 notificationStore.showError('权限不足')
272 next({ name: 'Home' })
273 return
274 }
275 }
276
277 // 已登录用户访问认证页面时重定向
278 if (to.path.startsWith('/auth') && userStore.isAuthenticated) {
279 next({ name: 'Home' })
280 return
281 }
282
283 next()
284})
285
286// 全局后置钩子
287router.afterEach((to, from) => {
288 // 页面访问统计
289 if (typeof gtag !== 'undefined') {
290 gtag('config', 'GA_MEASUREMENT_ID', {
291 page_title: to.meta.title,
292 page_location: window.location.href
293 })
294 }
295
296 // 清除加载状态
297 const loadingStore = useLoadingStore()
298 loadingStore.setLoading(false)
299})
300
301// 路由错误处理
302router.onError((error) => {
303 console.error('Router error:', error)
304 const notificationStore = useNotificationStore()
305 notificationStore.showError('页面加载失败')
306})
307
308export default router
309
310// 路由工具函数
311export const routeUtils = {
312 // 检查当前路由是否匹配
313 isCurrentRoute(routeName: string): boolean {
314 return router.currentRoute.value.name === routeName
315 },
316
317 // 检查是否为子路由
318 isChildRoute(parentName: string): boolean {
319 const current = router.currentRoute.value
320 return current.matched.some(route => route.name === parentName)
321 },
322
323 // 获取面包屑导航
324 getBreadcrumbs() {
325 const route = router.currentRoute.value
326 return route.matched
327 .filter(record => record.meta?.title)
328 .map(record => ({
329 title: record.meta.title,
330 name: record.name,
331 path: record.path
332 }))
333 },
334
335 // 生成菜单项
336 generateMenuItems() {
337 const userStore = useUserStore()
338
339 const filterRoutes = (routes: RouteRecordRaw[]): any[] => {
340 return routes
341 .filter(route => {
342 // 检查是否显示在菜单中
343 if (!route.meta?.showInMenu) return false
344
345 // 检查权限
346 if (route.meta.requiresAuth && !userStore.isAuthenticated) return false
347 if (route.meta.roles && !userStore.hasAnyRole(route.meta.roles)) return false
348 if (route.meta.permissions && !userStore.hasAnyPermission(route.meta.permissions)) return false
349
350 return true
351 })
352 .map(route => ({
353 name: route.name,
354 title: route.meta?.title,
355 icon: route.meta?.icon,
356 path: route.path,
357 children: route.children ? filterRoutes(route.children) : []
358 }))
359 }
360
361 return filterRoutes(routes)
362 }
363}

4. Vue性能优化与最佳实践

4.1 性能优化策略

Vue 3在性能方面有了显著提升,但仍需要开发者采用正确的优化策略来构建高性能应用。

性能优化检查清单

优化类型技术方案适用场景性能提升实现难度
组件缓存KeepAlive频繁切换的组件⭐⭐⭐⭐⭐⭐
计算属性computed()复杂计算逻辑⭐⭐⭐⭐⭐
虚拟滚动自定义实现大列表渲染⭐⭐⭐⭐⭐⭐⭐⭐⭐
异步组件defineAsyncComponent代码分割⭐⭐⭐⭐⭐⭐
响应式优化shallowRef/shallowReactive大对象处理⭐⭐⭐⭐⭐⭐
Vue性能优化原则
  1. 测量优先:使用Vue DevTools分析性能瓶颈
  2. 渐进优化:从影响最大的优化开始
  3. 避免过早优化:在确认性能问题后再优化
  4. 用户体验导向:关注用户感知的性能指标
  5. 持续监控:建立性能监控和告警机制

Vue.js作为现代前端开发的重要框架,其组合式API、响应式系统和丰富的生态系统为开发者提供了强大的工具。通过掌握Vue 3的核心特性、组件化开发模式和性能优化技巧,可以构建出高质量、高性能的现代Web应用。

参与讨论