🚀 引言

Vue 3作为Vue.js框架的重大版本更新,带来了许多革命性的新特性和改进。从全新的Composition API到更强大的响应式系统,从更好的TypeScript支持到显著的性能提升,Vue 3为现代前端开发提供了更加灵活和强大的工具。

本文将深入探讨Vue 3的核心新特性,通过实际代码示例和最佳实践,帮助你全面掌握Vue 3的精髓,并顺利完成从Vue 2到Vue 3的升级之路。

📚 Composition API核心概念

1. setup函数基础

Composition API是Vue 3最重要的新特性之一,它提供了一种更灵活的方式来组织组件逻辑。

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
<!-- UserProfile.vue -->
<template>
<div class="user-profile">
<div class="user-info">
<img :src="user.avatar" :alt="user.name" class="avatar" />
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
</div>

<div class="user-stats">
<div class="stat-item">
<span class="label">Posts</span>
<span class="value">{{ userStats.posts }}</span>
</div>
<div class="stat-item">
<span class="label">Followers</span>
<span class="value">{{ userStats.followers }}</span>
</div>
<div class="stat-item">
<span class="label">Following</span>
<span class="value">{{ userStats.following }}</span>
</div>
</div>

<div class="actions">
<button @click="followUser" :disabled="isFollowing">
{{ isFollowing ? 'Following...' : 'Follow' }}
</button>
<button @click="sendMessage">Send Message</button>
</div>

<div class="posts" v-if="posts.length">
<h3>Recent Posts</h3>
<div v-for="post in posts" :key="post.id" class="post-item">
<h4>{{ post.title }}</h4>
<p>{{ post.excerpt }}</p>
<span class="post-date">{{ formatDate(post.createdAt) }}</span>
</div>
</div>

<div v-if="loading" class="loading">Loading...</div>
</div>
</template>

<script>
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { useUserApi } from '@/composables/useUserApi'
import { useNotification } from '@/composables/useNotification'

export default {
name: 'UserProfile',
props: {
userId: {
type: String,
required: true
}
},
setup(props) {
// 响应式数据
const user = reactive({
id: '',
name: '',
email: '',
avatar: ''
})

const userStats = reactive({
posts: 0,
followers: 0,
following: 0
})

const posts = ref([])
const loading = ref(false)
const isFollowing = ref(false)

// 组合式函数
const { fetchUser, followUser: apiFollowUser, getUserPosts } = useUserApi()
const { showSuccess, showError } = useNotification()

// 计算属性
const isCurrentUser = computed(() => {
return user.id === getCurrentUserId()
})

// 方法
const loadUserData = async () => {
loading.value = true
try {
const userData = await fetchUser(props.userId)
Object.assign(user, userData)
Object.assign(userStats, userData.stats)

const userPosts = await getUserPosts(props.userId)
posts.value = userPosts
} catch (error) {
showError('Failed to load user data')
} finally {
loading.value = false
}
}

const followUser = async () => {
if (isFollowing.value) return

isFollowing.value = true
try {
await apiFollowUser(user.id)
userStats.followers++
showSuccess(`You are now following ${user.name}`)
} catch (error) {
showError('Failed to follow user')
} finally {
isFollowing.value = false
}
}

const sendMessage = () => {
// 实现发送消息逻辑
console.log('Send message to', user.name)
}

const formatDate = (date) => {
return new Date(date).toLocaleDateString()
}

const getCurrentUserId = () => {
// 获取当前用户ID的逻辑
return localStorage.getItem('currentUserId')
}

// 监听器
watch(() => props.userId, (newUserId) => {
if (newUserId) {
loadUserData()
}
}, { immediate: true })

// 生命周期
onMounted(() => {
console.log('UserProfile component mounted')
})

// 返回模板需要的数据和方法
return {
user,
userStats,
posts,
loading,
isFollowing,
isCurrentUser,
followUser,
sendMessage,
formatDate
}
}
}
</script>

<style scoped>
.user-profile {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}

.user-info {
text-align: center;
margin-bottom: 30px;
}

.avatar {
width: 120px;
height: 120px;
border-radius: 50%;
object-fit: cover;
margin-bottom: 15px;
}

.user-stats {
display: flex;
justify-content: space-around;
margin-bottom: 30px;
padding: 20px;
background: #f5f5f5;
border-radius: 8px;
}

.stat-item {
text-align: center;
}

.stat-item .label {
display: block;
font-size: 14px;
color: #666;
margin-bottom: 5px;
}

.stat-item .value {
display: block;
font-size: 24px;
font-weight: bold;
color: #333;
}

.actions {
display: flex;
gap: 10px;
justify-content: center;
margin-bottom: 30px;
}

.actions button {
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}

.actions button:first-child {
background: #007bff;
color: white;
}

.actions button:last-child {
background: #6c757d;
color: white;
}

.posts {
margin-top: 30px;
}

.post-item {
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
margin-bottom: 10px;
}

.post-date {
font-size: 12px;
color: #666;
}

.loading {
text-align: center;
padding: 20px;
color: #666;
}
</style>

2. 组合式函数(Composables)

组合式函数是Vue 3中复用逻辑的推荐方式:

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
51
52
53
// composables/useUserApi.js
import { ref } from 'vue'
import axios from 'axios'

export function useUserApi() {
const loading = ref(false)
const error = ref(null)

const fetchUser = async (userId) => {
loading.value = true
error.value = null

try {
const response = await axios.get(`/api/users/${userId}`)
return response.data
} catch (err) {
error.value = err.message
throw err
} finally {
loading.value = false
}
}

const followUser = async (userId) => {
try {
const response = await axios.post(`/api/users/${userId}/follow`)
return response.data
} catch (err) {
error.value = err.message
throw err
}
}

const getUserPosts = async (userId, limit = 10) => {
try {
const response = await axios.get(`/api/users/${userId}/posts`, {
params: { limit }
})
return response.data
} catch (err) {
error.value = err.message
throw err
}
}

return {
loading,
error,
fetchUser,
followUser,
getUserPosts
}
}
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
// composables/useNotification.js
import { ref } from 'vue'

const notifications = ref([])

export function useNotification() {
const showNotification = (message, type = 'info', duration = 3000) => {
const id = Date.now()
const notification = {
id,
message,
type,
visible: true
}

notifications.value.push(notification)

setTimeout(() => {
removeNotification(id)
}, duration)

return id
}

const removeNotification = (id) => {
const index = notifications.value.findIndex(n => n.id === id)
if (index > -1) {
notifications.value.splice(index, 1)
}
}

const showSuccess = (message) => showNotification(message, 'success')
const showError = (message) => showNotification(message, 'error')
const showWarning = (message) => showNotification(message, 'warning')

return {
notifications,
showNotification,
removeNotification,
showSuccess,
showError,
showWarning
}
}

3. 响应式系统深入

Vue 3的响应式系统基于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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// composables/useReactiveState.js
import { ref, reactive, computed, watch, watchEffect, toRefs } from 'vue'

export function useReactiveState() {
// ref - 基本类型响应式
const count = ref(0)
const message = ref('Hello Vue 3')

// reactive - 对象响应式
const state = reactive({
user: {
name: 'John Doe',
age: 30,
preferences: {
theme: 'dark',
language: 'en'
}
},
todos: [
{ id: 1, text: 'Learn Vue 3', completed: false },
{ id: 2, text: 'Build an app', completed: false }
],
filters: {
status: 'all',
search: ''
}
})

// 计算属性
const doubleCount = computed(() => count.value * 2)

const filteredTodos = computed(() => {
let filtered = state.todos

// 按状态筛选
if (state.filters.status === 'active') {
filtered = filtered.filter(todo => !todo.completed)
} else if (state.filters.status === 'completed') {
filtered = filtered.filter(todo => todo.completed)
}

// 按搜索词筛选
if (state.filters.search) {
filtered = filtered.filter(todo =>
todo.text.toLowerCase().includes(state.filters.search.toLowerCase())
)
}

return filtered
})

const completedCount = computed(() =>
state.todos.filter(todo => todo.completed).length
)

const remainingCount = computed(() =>
state.todos.length - completedCount.value
)

// 方法
const increment = () => {
count.value++
}

const addTodo = (text) => {
const newTodo = {
id: Date.now(),
text: text.trim(),
completed: false
}
state.todos.push(newTodo)
}

const removeTodo = (id) => {
const index = state.todos.findIndex(todo => todo.id === id)
if (index > -1) {
state.todos.splice(index, 1)
}
}

const toggleTodo = (id) => {
const todo = state.todos.find(todo => todo.id === id)
if (todo) {
todo.completed = !todo.completed
}
}

const clearCompleted = () => {
state.todos = state.todos.filter(todo => !todo.completed)
}

const updateUserPreference = (key, value) => {
state.user.preferences[key] = value
}

// 监听器
watch(count, (newCount, oldCount) => {
console.log(`Count changed from ${oldCount} to ${newCount}`)
})

watch(() => state.user.name, (newName) => {
console.log(`User name changed to: ${newName}`)
})

// 深度监听
watch(state.todos, (newTodos) => {
localStorage.setItem('todos', JSON.stringify(newTodos))
}, { deep: true })

// watchEffect - 自动追踪依赖
watchEffect(() => {
document.title = `Todos (${remainingCount.value} remaining)`
})

// 使用toRefs解构reactive对象
const userRefs = toRefs(state.user)

return {
// 基本响应式数据
count,
message,
state,

// 计算属性
doubleCount,
filteredTodos,
completedCount,
remainingCount,

// 方法
increment,
addTodo,
removeTodo,
toggleTodo,
clearCompleted,
updateUserPreference,

// 解构的用户数据
...userRefs
}
}

🎯 新特性深度解析

1. Teleport组件

Teleport允许我们将组件的一部分渲染到DOM的其他位置:

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
<!-- Modal.vue -->
<template>
<teleport to="body">
<div v-if="visible" class="modal-overlay" @click="closeModal">
<div class="modal-content" @click.stop>
<div class="modal-header">
<h3>{{ title }}</h3>
<button @click="closeModal" class="close-btn">&times;</button>
</div>
<div class="modal-body">
<slot></slot>
</div>
<div class="modal-footer" v-if="$slots.footer">
<slot name="footer"></slot>
</div>
</div>
</div>
</teleport>
</template>

<script>
import { watch } from 'vue'

export default {
name: 'Modal',
props: {
visible: {
type: Boolean,
default: false
},
title: {
type: String,
default: 'Modal'
},
closeOnEscape: {
type: Boolean,
default: true
}
},
emits: ['update:visible', 'close'],
setup(props, { emit }) {
const closeModal = () => {
emit('update:visible', false)
emit('close')
}

const handleEscape = (event) => {
if (event.key === 'Escape' && props.closeOnEscape) {
closeModal()
}
}

// 监听visible变化,管理body滚动和键盘事件
watch(() => props.visible, (visible) => {
if (visible) {
document.body.style.overflow = 'hidden'
document.addEventListener('keydown', handleEscape)
} else {
document.body.style.overflow = ''
document.removeEventListener('keydown', handleEscape)
}
})

return {
closeModal
}
}
}
</script>

<style scoped>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}

.modal-content {
background: white;
border-radius: 8px;
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}

.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #eee;
}

.modal-header h3 {
margin: 0;
}

.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #666;
}

.modal-body {
padding: 20px;
}

.modal-footer {
padding: 20px;
border-top: 1px solid #eee;
text-align: right;
}
</style>
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
<!-- 使用Modal组件 -->
<template>
<div class="app">
<button @click="showModal = true">Open Modal</button>

<Modal v-model:visible="showModal" title="Confirm Action">
<p>Are you sure you want to delete this item?</p>

<template #footer>
<button @click="showModal = false">Cancel</button>
<button @click="confirmDelete" class="danger">Delete</button>
</template>
</Modal>
</div>
</template>

<script>
import { ref } from 'vue'
import Modal from './components/Modal.vue'

export default {
components: {
Modal
},
setup() {
const showModal = ref(false)

const confirmDelete = () => {
// 执行删除操作
console.log('Item deleted')
showModal.value = false
}

return {
showModal,
confirmDelete
}
}
}
</script>

2. Suspense组件

Suspense用于处理异步组件的加载状态:

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
<!-- AsyncUserList.vue -->
<template>
<div class="user-list">
<h2>User List</h2>
<div v-for="user in users" :key="user.id" class="user-item">
<img :src="user.avatar" :alt="user.name" />
<div class="user-info">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
</div>
</div>
</template>

<script>
import { ref } from 'vue'

export default {
name: 'AsyncUserList',
async setup() {
const users = ref([])

// 模拟异步数据获取
const fetchUsers = async () => {
const response = await fetch('/api/users')
return response.json()
}

// 在setup中使用await
users.value = await fetchUsers()

return {
users
}
}
}
</script>
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
51
<!-- App.vue -->
<template>
<div class="app">
<Suspense>
<template #default>
<AsyncUserList />
</template>

<template #fallback>
<div class="loading-container">
<div class="spinner"></div>
<p>Loading users...</p>
</div>
</template>
</Suspense>
</div>
</template>

<script>
import { defineAsyncComponent } from 'vue'

export default {
components: {
AsyncUserList: defineAsyncComponent(() => import('./components/AsyncUserList.vue'))
}
}
</script>

<style>
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 200px;
}

.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}

@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>

3. Fragment支持

Vue 3支持多个根节点:

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
<!-- MultipleRoots.vue -->
<template>
<!-- Vue 3中可以有多个根节点 -->
<header class="component-header">
<h1>{{ title }}</h1>
</header>

<main class="component-content">
<slot></slot>
</main>

<footer class="component-footer">
<p>&copy; 2025 My App</p>
</footer>
</template>

<script>
export default {
name: 'MultipleRoots',
props: {
title: {
type: String,
required: true
}
}
}
</script>

🔧 TypeScript集成

Vue 3对TypeScript提供了更好的支持:

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
// types/user.ts
export interface User {
id: string
name: string
email: string
avatar: string
createdAt: Date
updatedAt: Date
}

export interface UserStats {
posts: number
followers: number
following: number
}

export interface Post {
id: string
title: string
content: string
excerpt: string
author: User
createdAt: Date
updatedAt: Date
tags: string[]
}
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
<!-- TypedComponent.vue -->
<template>
<div class="typed-component">
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>

<div class="posts">
<article v-for="post in posts" :key="post.id" class="post">
<h3>{{ post.title }}</h3>
<p>{{ post.excerpt }}</p>
<div class="tags">
<span v-for="tag in post.tags" :key="tag" class="tag">
{{ tag }}
</span>
</div>
</article>
</div>
</div>
</template>

<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import type { User, Post, UserStats } from '@/types/user'

// Props定义
interface Props {
userId: string
showStats?: boolean
}

const props = withDefaults(defineProps<Props>(), {
showStats: true
})

// Emits定义
interface Emits {
(e: 'userLoaded', user: User): void
(e: 'error', error: Error): void
}

const emit = defineEmits<Emits>()

// 响应式数据
const user = ref<User | null>(null)
const posts = ref<Post[]>([])
const stats = reactive<UserStats>({
posts: 0,
followers: 0,
following: 0
})
const loading = ref<boolean>(false)

// 计算属性
const userDisplayName = computed<string>(() => {
return user.value ? user.value.name : 'Unknown User'
})

const sortedPosts = computed<Post[]>(() => {
return [...posts.value].sort((a, b) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
)
})

// 方法
const fetchUserData = async (): Promise<void> => {
loading.value = true

try {
const response = await fetch(`/api/users/${props.userId}`)
if (!response.ok) {
throw new Error('Failed to fetch user data')
}

const userData: User = await response.json()
user.value = userData

emit('userLoaded', userData)

// 获取用户文章
await fetchUserPosts()

} catch (error) {
emit('error', error as Error)
} finally {
loading.value = false
}
}

const fetchUserPosts = async (): Promise<void> => {
try {
const response = await fetch(`/api/users/${props.userId}/posts`)
if (!response.ok) {
throw new Error('Failed to fetch posts')
}

const postsData: Post[] = await response.json()
posts.value = postsData

// 更新统计信息
stats.posts = postsData.length

} catch (error) {
console.error('Error fetching posts:', error)
}
}

// 生命周期
onMounted(() => {
fetchUserData()
})

// 暴露给模板的数据
defineExpose({
user,
posts,
refreshData: fetchUserData
})
</script>
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
51
52
53
54
55
56
57
58
59
60
61
62
63
// composables/useTypedApi.ts
import { ref, type Ref } from 'vue'
import type { User, Post } from '@/types/user'

interface ApiResponse<T> {
data: T
message: string
success: boolean
}

interface UseApiReturn<T> {
data: Ref<T | null>
loading: Ref<boolean>
error: Ref<string | null>
execute: () => Promise<void>
}

export function useTypedApi<T>(url: string): UseApiReturn<T> {
const data = ref<T | null>(null)
const loading = ref<boolean>(false)
const error = ref<string | null>(null)

const execute = async (): Promise<void> => {
loading.value = true
error.value = null

try {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}

const result: ApiResponse<T> = await response.json()

if (result.success) {
data.value = result.data
} else {
throw new Error(result.message)
}

} catch (err) {
error.value = err instanceof Error ? err.message : 'Unknown error'
} finally {
loading.value = false
}
}

return {
data,
loading,
error,
execute
}
}

// 特定类型的API hooks
export function useUserApi(userId: string) {
return useTypedApi<User>(`/api/users/${userId}`)
}

export function usePostsApi(userId: string) {
return useTypedApi<Post[]>(`/api/users/${userId}/posts`)
}

⚡ 性能优化策略

1. 响应式优化

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// composables/usePerformanceOptimization.js
import {
ref,
reactive,
computed,
watch,
shallowRef,
shallowReactive,
readonly,
markRaw,
nextTick
} from 'vue'

export function usePerformanceOptimization() {
// 1. 使用shallowRef优化大型对象
const largeDataSet = shallowRef({
items: new Array(10000).fill(0).map((_, i) => ({
id: i,
name: `Item ${i}`,
data: new Array(100).fill(0)
}))
})

// 2. 使用shallowReactive优化嵌套对象
const shallowState = shallowReactive({
user: { name: 'John', age: 30 },
settings: { theme: 'dark', lang: 'en' },
cache: new Map()
})

// 3. 使用readonly防止意外修改
const readonlyConfig = readonly({
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
})

// 4. 使用markRaw标记非响应式对象
const thirdPartyLib = markRaw({
instance: new SomeThirdPartyLibrary(),
config: { /* 大型配置对象 */ }
})

// 5. 计算属性缓存优化
const expensiveComputation = computed(() => {
console.log('Computing expensive value...')
return largeDataSet.value.items.reduce((sum, item) => {
return sum + item.data.length
}, 0)
})

// 6. 防抖计算属性
const debouncedSearch = ref('')
const searchResults = ref([])

let searchTimeout
watch(debouncedSearch, (newValue) => {
clearTimeout(searchTimeout)
searchTimeout = setTimeout(async () => {
if (newValue.trim()) {
searchResults.value = await performSearch(newValue)
} else {
searchResults.value = []
}
}, 300)
})

// 7. 虚拟滚动实现
const virtualScrollData = ref({
items: new Array(100000).fill(0).map((_, i) => ({
id: i,
text: `Item ${i}`
})),
visibleStart: 0,
visibleEnd: 50,
itemHeight: 40,
containerHeight: 400
})

const visibleItems = computed(() => {
const { items, visibleStart, visibleEnd } = virtualScrollData.value
return items.slice(visibleStart, visibleEnd)
})

const handleScroll = (event) => {
const scrollTop = event.target.scrollTop
const { itemHeight, containerHeight } = virtualScrollData.value

const visibleStart = Math.floor(scrollTop / itemHeight)
const visibleCount = Math.ceil(containerHeight / itemHeight)
const visibleEnd = visibleStart + visibleCount + 5 // 缓冲区

virtualScrollData.value.visibleStart = visibleStart
virtualScrollData.value.visibleEnd = Math.min(visibleEnd, virtualScrollData.value.items.length)
}

// 8. 批量更新优化
const batchUpdate = async (updates) => {
// 使用nextTick确保DOM更新完成
await nextTick()

// 批量应用更新
updates.forEach(update => {
Object.assign(shallowState, update)
})
}

// 9. 内存泄漏预防
const cleanup = () => {
clearTimeout(searchTimeout)
// 清理其他资源
}

const performSearch = async (query) => {
// 模拟搜索API调用
await new Promise(resolve => setTimeout(resolve, 100))
return largeDataSet.value.items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
).slice(0, 20)
}

return {
largeDataSet,
shallowState,
readonlyConfig,
thirdPartyLib,
expensiveComputation,
debouncedSearch,
searchResults,
virtualScrollData,
visibleItems,
handleScroll,
batchUpdate,
cleanup
}
}

2. 组件优化

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
<!-- OptimizedList.vue -->
<template>
<div class="optimized-list">
<!-- 虚拟滚动容器 -->
<div
class="virtual-scroll-container"
:style="{ height: containerHeight + 'px' }"
@scroll="handleScroll"
>
<!-- 占位空间 -->
<div :style="{ height: totalHeight + 'px', position: 'relative' }">
<!-- 可见项目 -->
<div
v-for="item in visibleItems"
:key="item.id"
class="list-item"
:style="{
position: 'absolute',
top: getItemTop(item.index) + 'px',
height: itemHeight + 'px'
}"
>
<ListItem :item="item" @click="handleItemClick" />
</div>
</div>
</div>
</div>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import ListItem from './ListItem.vue'

const props = defineProps({
items: {
type: Array,
required: true
},
itemHeight: {
type: Number,
default: 50
},
containerHeight: {
type: Number,
default: 400
}
})

const emit = defineEmits(['item-click'])

// 响应式数据
const scrollTop = ref(0)
const visibleStart = ref(0)
const visibleEnd = ref(0)

// 计算属性
const totalHeight = computed(() => props.items.length * props.itemHeight)

const visibleCount = computed(() =>
Math.ceil(props.containerHeight / props.itemHeight)
)

const visibleItems = computed(() => {
const start = Math.max(0, visibleStart.value - 5) // 缓冲区
const end = Math.min(props.items.length, visibleEnd.value + 5)

return props.items.slice(start, end).map((item, index) => ({
...item,
index: start + index
}))
})

// 方法
const handleScroll = (event) => {
scrollTop.value = event.target.scrollTop

const start = Math.floor(scrollTop.value / props.itemHeight)
const end = start + visibleCount.value

visibleStart.value = start
visibleEnd.value = end
}

const getItemTop = (index) => {
return index * props.itemHeight
}

const handleItemClick = (item) => {
emit('item-click', item)
}

// 初始化
onMounted(() => {
visibleEnd.value = visibleCount.value
})
</script>

<style scoped>
.virtual-scroll-container {
overflow-y: auto;
border: 1px solid #ddd;
}

.list-item {
width: 100%;
border-bottom: 1px solid #eee;
display: flex;
align-items: center;
padding: 0 15px;
}
</style>

🔄 迁移指南

1. 从Vue 2迁移到Vue 3

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// migration-helper.js - 迁移辅助工具

// Vue 2 Options API 转换为 Vue 3 Composition API
export function convertOptionsToComposition(optionsComponent) {
const {
data,
computed,
methods,
watch,
created,
mounted,
beforeDestroy,
destroyed,
...rest
} = optionsComponent

return {
...rest,
setup() {
// 转换data
const state = reactive(typeof data === 'function' ? data() : data || {})

// 转换computed
const computedProps = {}
if (computed) {
Object.keys(computed).forEach(key => {
computedProps[key] = computed(() => computed[key].call(state))
})
}

// 转换methods
const methodsObj = {}
if (methods) {
Object.keys(methods).forEach(key => {
methodsObj[key] = methods[key].bind(state)
})
}

// 转换watch
if (watch) {
Object.keys(watch).forEach(key => {
watch(() => state[key], watch[key])
})
}

// 转换生命周期
if (created) onBeforeMount(created)
if (mounted) onMounted(mounted)
if (beforeDestroy) onBeforeUnmount(beforeDestroy)
if (destroyed) onUnmounted(destroyed)

return {
...state,
...computedProps,
...methodsObj
}
}
}
}

// 兼容性检查工具
export function checkCompatibility() {
const issues = []

// 检查全局API变化
if (typeof Vue !== 'undefined' && Vue.version.startsWith('2')) {
issues.push({
type: 'global-api',
message: 'Vue 3 requires createApp() instead of new Vue()',
solution: 'Replace new Vue() with createApp()'
})
}

// 检查过滤器使用
const templates = document.querySelectorAll('template, script')
templates.forEach(template => {
if (template.innerHTML.includes('|')) {
issues.push({
type: 'filters',
message: 'Filters are removed in Vue 3',
solution: 'Replace filters with computed properties or methods'
})
}
})

return issues
}

2. 渐进式迁移策略

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// progressive-migration.js
import { createApp } from 'vue'
import { createCompatVue } from '@vue/compat'

// 1. 兼容模式配置
const app = createCompatVue({
MODE: 2, // 使用Vue 2兼容模式
GLOBAL_MOUNT: false,
GLOBAL_EXTEND: false,
GLOBAL_PROTOTYPE: false,
GLOBAL_SET: false,
GLOBAL_DELETE: false,
GLOBAL_OBSERVABLE: false,
INSTANCE_SET: false,
INSTANCE_DELETE: false,
INSTANCE_DESTROY: false,
INSTANCE_EVENT_EMITTER: false,
INSTANCE_EVENT_HOOKS: false,
INSTANCE_CHILDREN: false,
INSTANCE_LISTENERS: false,
INSTANCE_SCOPED_SLOTS: false,
PROPS_DEFAULT_THIS: false,
CUSTOM_DIR: false,
ATTR_FALSE_VALUE: false,
ATTR_ENUMERATED_COERCION: false,
TRANSITION_CLASSES: false,
COMPONENT_ASYNC: false,
COMPONENT_FUNCTIONAL: false,
COMPONENT_V_MODEL: false,
RENDER_FUNCTION: false,
FILTERS: false,
PRIVATE_APIS: false
})

// 2. 分步骤迁移计划
const migrationSteps = [
{
step: 1,
description: '升级构建工具和依赖',
tasks: [
'Update Vue to 3.x',
'Update Vue Router to 4.x',
'Update Vuex to 4.x or migrate to Pinia',
'Update build tools (Vite, Webpack 5)',
'Update ESLint rules'
]
},
{
step: 2,
description: '启用兼容模式',
tasks: [
'Install @vue/compat',
'Configure compatibility mode',
'Test existing functionality',
'Fix breaking changes gradually'
]
},
{
step: 3,
description: '迁移组件到Composition API',
tasks: [
'Start with leaf components',
'Convert Options API to Composition API',
'Extract reusable logic to composables',
'Update TypeScript types'
]
},
{
step: 4,
description: '优化和现代化',
tasks: [
'Implement new Vue 3 features',
'Optimize performance',
'Remove compatibility mode',
'Update documentation'
]
}
]

// 3. 自动化迁移工具
export class MigrationTool {
constructor() {
this.issues = []
this.fixes = []
}

analyzeComponent(componentCode) {
const issues = []

// 检查过滤器
if (componentCode.includes('filters:')) {
issues.push({
type: 'filters',
line: this.findLineNumber(componentCode, 'filters:'),
message: 'Filters are removed in Vue 3'
})
}

// 检查$listeners
if (componentCode.includes('$listeners')) {
issues.push({
type: 'listeners',
line: this.findLineNumber(componentCode, '$listeners'),
message: '$listeners is removed, use $attrs instead'
})
}

// 检查.sync修饰符
if (componentCode.includes('.sync')) {
issues.push({
type: 'sync',
line: this.findLineNumber(componentCode, '.sync'),
message: '.sync is replaced with v-model'
})
}

return issues
}

generateFixes(issues) {
return issues.map(issue => {
switch (issue.type) {
case 'filters':
return {
...issue,
fix: 'Convert filter to computed property or method'
}
case 'listeners':
return {
...issue,
fix: 'Replace $listeners with $attrs'
}
case 'sync':
return {
...issue,
fix: 'Replace .sync with v-model:propName'
}
default:
return issue
}
})
}

findLineNumber(code, searchString) {
const lines = code.split('\n')
for (let i = 0; i < lines.length; i++) {
if (lines[i].includes(searchString)) {
return i + 1
}
}
return -1
}
}

🎯 最佳实践总结

1. 开发规范

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// vue3-best-practices.js

// 1. 组合式函数命名规范
export const composableNamingRules = {
// ✅ 好的命名
useUserData: () => ({ /* 用户数据逻辑 */ }),
useApiRequest: () => ({ /* API请求逻辑 */ }),
useLocalStorage: () => ({ /* 本地存储逻辑 */ }),

// ❌ 避免的命名
userData: () => ({ /* 不清晰 */ }),
api: () => ({ /* 太简单 */ }),
helper: () => ({ /* 太泛化 */ })
}

// 2. 响应式数据组织
export const reactiveDataOrganization = {
// ✅ 推荐:按功能分组
goodExample: () => {
const userState = reactive({
profile: null,
preferences: {},
isLoading: false
})

const uiState = reactive({
sidebarOpen: false,
theme: 'light',
notifications: []
})

return { userState, uiState }
},

// ❌ 避免:混合不相关的状态
badExample: () => {
const state = reactive({
user: null,
sidebarOpen: false,
posts: [],
theme: 'light',
isLoading: false
})

return { state }
}
}

// 3. 计算属性优化
export const computedOptimization = {
// ✅ 缓存昂贵的计算
expensiveComputed: computed(() => {
return largeArray.value.filter(item =>
item.category === selectedCategory.value
).sort((a, b) => b.score - a.score)
}),

// ✅ 避免在计算属性中修改状态
safeComputed: computed(() => {
return items.value.length > 0 ? 'Has items' : 'No items'
}),

// ❌ 避免:在计算属性中产生副作用
unsafeComputed: computed(() => {
// 不要这样做!
if (items.value.length === 0) {
showEmptyMessage.value = true // 副作用
}
return items.value.length
})
}

2. 性能优化清单

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
# Vue 3 性能优化清单

## 响应式优化
- [ ] 使用 `shallowRef` 处理大型不可变数据
- [ ] 使用 `shallowReactive` 处理嵌套对象
- [ ] 使用 `readonly` 保护配置数据
- [ ] 使用 `markRaw` 标记第三方库实例

## 组件优化
- [ ] 实现虚拟滚动处理大列表
- [ ] 使用 `v-memo` 缓存复杂渲染
- [ ] 合理使用 `keep-alive` 缓存组件
- [ ] 避免不必要的组件重新渲染

## 代码分割
- [ ] 使用动态导入分割路由组件
- [ ] 实现组件懒加载
- [ ] 优化第三方库的导入

## 构建优化
- [ ] 启用生产环境构建优化
- [ ] 配置合适的 chunk 分割策略
- [ ] 使用 CDN 加载大型依赖
- [ ] 启用 gzip/brotli 压缩

## 运行时优化
- [ ] 避免频繁的 DOM 操作
- [ ] 使用事件委托减少事件监听器
- [ ] 实现防抖和节流
- [ ] 及时清理定时器和事件监听器

📝 总结

Vue 3带来了许多激动人心的新特性和改进,从Composition API的灵活性到更好的TypeScript支持,从性能提升到新的内置组件。通过本文的学习,我们深入了解了:

  1. Composition API:提供了更灵活的逻辑组织方式
  2. 新特性:Teleport、Suspense、Fragment等强大功能
  3. TypeScript集成:更好的类型安全和开发体验
  4. 性能优化:多种优化策略和最佳实践
  5. 迁移指南:从Vue 2平滑过渡到Vue 3

Vue 3不仅仅是一个版本升级,更是前端开发思维的进化。它鼓励我们:

  • 组合优于继承:通过组合式函数复用逻辑
  • 类型安全:利用TypeScript提高代码质量
  • 性能意识:关注应用的运行时性能
  • 渐进增强:逐步采用新特性和最佳实践

随着Vue 3生态系统的不断完善,它将继续为现代前端开发提供强大的支持。掌握这些技术,将让你在Vue.js的世界中游刃有余,构建出更加优秀的用户界面和用户体验。


Vue 3的学习之路永无止境,保持好奇心,持续实践,你将发现更多可能性! ⚡