Vue 3 Composition API 深度应用与高级模式
Orion K Lv6

Composition API 是 Vue 3 最重要的新特性之一,它为组件逻辑的组织和复用提供了全新的方式。本文将深入探讨 Composition API 的高级应用模式,包括组合式函数的设计原则、响应式系统的深度使用、以及在大型项目中的最佳实践。

Composition API 核心概念回顾

1. 基础响应式 API

ref vs reactive 的选择策略

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
// composables/useCounter.js
import { ref, reactive, computed, watch } from 'vue'

// 基础类型使用 ref
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const isEven = computed(() => count.value % 2 === 0)

const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue

// 监听变化
watch(count, (newValue, oldValue) => {
console.log(`计数器从 ${oldValue} 变为 ${newValue}`)
})

return {
count: readonly(count), // 只读暴露
isEven,
increment,
decrement,
reset
}
}

// 复杂对象使用 reactive
export function useUserProfile() {
const profile = reactive({
personal: {
name: '',
email: '',
avatar: ''
},
preferences: {
theme: 'light',
language: 'zh-CN',
notifications: {
email: true,
push: false,
sms: false
}
},
stats: {
loginCount: 0,
lastLoginAt: null
}
})

// 计算属性
const displayName = computed(() => {
return profile.personal.name || profile.personal.email || '未知用户'
})

const isActiveUser = computed(() => {
const lastLogin = profile.stats.lastLoginAt
if (!lastLogin) return false

const daysSinceLogin = (Date.now() - lastLogin) / (1000 * 60 * 60 * 24)
return daysSinceLogin <= 30
})

// 方法
const updatePersonal = (data) => {
Object.assign(profile.personal, data)
}

const updatePreferences = (data) => {
Object.assign(profile.preferences, data)
}

const recordLogin = () => {
profile.stats.loginCount++
profile.stats.lastLoginAt = Date.now()
}

return {
profile: readonly(profile),
displayName,
isActiveUser,
updatePersonal,
updatePreferences,
recordLogin
}
}

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
// composables/useReactiveState.js
import { reactive, computed, watch, toRefs } from 'vue'

// 创建带有历史记录的响应式状态
export function useStateWithHistory(initialState) {
const state = reactive({
current: { ...initialState },
history: [{ ...initialState }],
currentIndex: 0
})

// 计算属性
const canUndo = computed(() => state.currentIndex > 0)
const canRedo = computed(() => state.currentIndex < state.history.length - 1)
const hasChanges = computed(() => state.currentIndex > 0)

// 监听当前状态变化
watch(
() => state.current,
(newState) => {
// 如果不是通过 undo/redo 操作触发的变化
if (!state._isNavigating) {
// 清除后续历史记录
state.history.splice(state.currentIndex + 1)
// 添加新状态到历史记录
state.history.push({ ...newState })
state.currentIndex = state.history.length - 1

// 限制历史记录长度
if (state.history.length > 50) {
state.history.shift()
state.currentIndex--
}
}
},
{ deep: true }
)

// 撤销操作
const undo = () => {
if (canUndo.value) {
state._isNavigating = true
state.currentIndex--
Object.assign(state.current, state.history[state.currentIndex])
nextTick(() => {
state._isNavigating = false
})
}
}

// 重做操作
const redo = () => {
if (canRedo.value) {
state._isNavigating = true
state.currentIndex++
Object.assign(state.current, state.history[state.currentIndex])
nextTick(() => {
state._isNavigating = false
})
}
}

// 重置到初始状态
const reset = () => {
state._isNavigating = true
Object.assign(state.current, initialState)
state.history = [{ ...initialState }]
state.currentIndex = 0
nextTick(() => {
state._isNavigating = false
})
}

// 获取历史记录
const getHistory = () => [...state.history]

// 跳转到指定历史记录
const goToHistory = (index) => {
if (index >= 0 && index < state.history.length) {
state._isNavigating = true
state.currentIndex = index
Object.assign(state.current, state.history[index])
nextTick(() => {
state._isNavigating = false
})
}
}

return {
state: state.current,
canUndo,
canRedo,
hasChanges,
undo,
redo,
reset,
getHistory,
goToHistory
}
}

响应式数据验证

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
// composables/useValidation.js
import { reactive, computed, watch } from 'vue'

export function useValidation(data, rules) {
const errors = reactive({})
const touched = reactive({})

// 验证单个字段
const validateField = (field, value) => {
const fieldRules = rules[field]
if (!fieldRules) return true

const fieldErrors = []

for (const rule of fieldRules) {
const result = rule.validator(value, data)
if (!result) {
fieldErrors.push(rule.message)
}
}

if (fieldErrors.length > 0) {
errors[field] = fieldErrors
return false
} else {
delete errors[field]
return true
}
}

// 验证所有字段
const validateAll = () => {
let isValid = true

Object.keys(rules).forEach(field => {
const fieldValid = validateField(field, data[field])
if (!fieldValid) isValid = false
touched[field] = true
})

return isValid
}

// 监听数据变化进行验证
Object.keys(rules).forEach(field => {
watch(
() => data[field],
(value) => {
if (touched[field]) {
validateField(field, value)
}
}
)
})

// 计算属性
const isValid = computed(() => Object.keys(errors).length === 0)
const hasErrors = computed(() => Object.keys(errors).length > 0)
const errorCount = computed(() => Object.keys(errors).length)

// 获取字段错误
const getFieldError = (field) => errors[field]?.[0]
const hasFieldError = (field) => !!errors[field]

// 标记字段为已触摸
const touchField = (field) => {
touched[field] = true
validateField(field, data[field])
}

// 清除字段错误
const clearFieldError = (field) => {
delete errors[field]
}

// 清除所有错误
const clearErrors = () => {
Object.keys(errors).forEach(field => {
delete errors[field]
})
}

return {
errors: readonly(errors),
touched: readonly(touched),
isValid,
hasErrors,
errorCount,
validateField,
validateAll,
getFieldError,
hasFieldError,
touchField,
clearFieldError,
clearErrors
}
}

// 常用验证规则
export const validationRules = {
required: (message = '此字段为必填项') => ({
validator: (value) => {
if (typeof value === 'string') return value.trim().length > 0
if (Array.isArray(value)) return value.length > 0
return value != null && value !== ''
},
message
}),

email: (message = '请输入有效的邮箱地址') => ({
validator: (value) => {
if (!value) return true
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailRegex.test(value)
},
message
}),

minLength: (min, message) => ({
validator: (value) => {
if (!value) return true
return value.length >= min
},
message: message || `最少需要 ${min} 个字符`
}),

maxLength: (max, message) => ({
validator: (value) => {
if (!value) return true
return value.length <= max
},
message: message || `最多允许 ${max} 个字符`
}),

pattern: (regex, message = '格式不正确') => ({
validator: (value) => {
if (!value) return true
return regex.test(value)
},
message
}),

custom: (validator, message) => ({
validator,
message
})
}

高级组合式函数模式

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
// composables/useAsyncState.js
import { ref, computed, watch } from 'vue'

export function useAsyncState(asyncFunction, initialState = null, options = {}) {
const {
immediate = true,
resetOnExecute = true,
shallow = true,
delay = 0,
onError = (error) => console.error(error),
onSuccess = () => {}
} = options

const state = shallow ? shallowRef(initialState) : ref(initialState)
const isLoading = ref(false)
const error = ref(null)
const isReady = ref(false)

// 计算属性
const isSuccess = computed(() => isReady.value && !error.value)
const isError = computed(() => isReady.value && !!error.value)

// 执行异步函数
const execute = async (...args) => {
if (resetOnExecute) {
state.value = initialState
}

error.value = null
isLoading.value = true
isReady.value = false

try {
// 延迟执行
if (delay > 0) {
await new Promise(resolve => setTimeout(resolve, delay))
}

const result = await asyncFunction(...args)
state.value = result
onSuccess(result)
return result
} catch (err) {
error.value = err
onError(err)
throw err
} finally {
isLoading.value = false
isReady.value = true
}
}

// 立即执行
if (immediate) {
execute()
}

return {
state: readonly(state),
isLoading: readonly(isLoading),
error: readonly(error),
isReady: readonly(isReady),
isSuccess,
isError,
execute
}
}

// 使用示例
export function useUserData(userId) {
const fetchUser = async (id) => {
const response = await fetch(`/api/users/${id}`)
if (!response.ok) {
throw new Error(`获取用户信息失败: ${response.statusText}`)
}
return response.json()
}

const {
state: user,
isLoading,
error,
execute: refetchUser
} = useAsyncState(
fetchUser,
null,
{
immediate: false,
onError: (error) => {
console.error('用户数据加载失败:', error)
// 可以在这里添加错误上报逻辑
}
}
)

// 监听 userId 变化自动重新获取
watch(
() => userId,
(newId) => {
if (newId) {
refetchUser(newId)
}
},
{ immediate: true }
)

return {
user,
isLoading,
error,
refetchUser
}
}

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
154
155
156
157
158
159
160
161
162
163
// composables/useCache.js
import { ref, computed, watch } from 'vue'

class CacheManager {
constructor() {
this.cache = new Map()
this.timestamps = new Map()
this.subscribers = new Map()
}

set(key, value, ttl = 300000) { // 默认5分钟过期
this.cache.set(key, value)
this.timestamps.set(key, Date.now() + ttl)

// 通知订阅者
if (this.subscribers.has(key)) {
this.subscribers.get(key).forEach(callback => callback(value))
}
}

get(key) {
if (!this.cache.has(key)) return null

const timestamp = this.timestamps.get(key)
if (Date.now() > timestamp) {
this.delete(key)
return null
}

return this.cache.get(key)
}

delete(key) {
this.cache.delete(key)
this.timestamps.delete(key)

// 通知订阅者
if (this.subscribers.has(key)) {
this.subscribers.get(key).forEach(callback => callback(null))
}
}

subscribe(key, callback) {
if (!this.subscribers.has(key)) {
this.subscribers.set(key, new Set())
}
this.subscribers.get(key).add(callback)

// 返回取消订阅函数
return () => {
this.subscribers.get(key)?.delete(callback)
}
}

clear() {
this.cache.clear()
this.timestamps.clear()
this.subscribers.clear()
}
}

const globalCache = new CacheManager()

export function useCache(key, fetcher, options = {}) {
const {
ttl = 300000, // 5分钟
immediate = true,
staleWhileRevalidate = true
} = options

const data = ref(null)
const isLoading = ref(false)
const error = ref(null)
const isStale = ref(false)

// 从缓存获取数据
const getCachedData = () => {
const cached = globalCache.get(key)
if (cached) {
data.value = cached
return true
}
return false
}

// 获取新数据
const fetchData = async (force = false) => {
if (!force && !isStale.value && data.value) {
return data.value
}

isLoading.value = true
error.value = null

try {
const result = await fetcher()
data.value = result
globalCache.set(key, result, ttl)
isStale.value = false
return result
} catch (err) {
error.value = err
throw err
} finally {
isLoading.value = false
}
}

// 订阅缓存变化
const unsubscribe = globalCache.subscribe(key, (value) => {
if (value === null) {
isStale.value = true
if (staleWhileRevalidate) {
fetchData()
}
} else {
data.value = value
isStale.value = false
}
})

// 清理函数
onUnmounted(() => {
unsubscribe()
})

// 立即获取数据
if (immediate) {
if (!getCachedData()) {
fetchData()
}
}

return {
data: readonly(data),
isLoading: readonly(isLoading),
error: readonly(error),
isStale: readonly(isStale),
refresh: () => fetchData(true),
invalidate: () => globalCache.delete(key)
}
}

// 使用示例
export function useApiData(endpoint, params = {}) {
const cacheKey = computed(() => {
const paramString = new URLSearchParams(params).toString()
return `${endpoint}?${paramString}`
})

const fetcher = async () => {
const response = await fetch(cacheKey.value)
if (!response.ok) {
throw new Error(`API 请求失败: ${response.statusText}`)
}
return response.json()
}

return useCache(cacheKey.value, fetcher, {
ttl: 600000, // 10分钟
staleWhileRevalidate: true
})
}

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
// composables/useEventHandlers.js
import { ref, onMounted, onUnmounted } from 'vue'

// 防抖函数
export function useDebounce(fn, delay = 300) {
let timeoutId = null

const debouncedFn = (...args) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => fn(...args), delay)
}

const cancel = () => {
clearTimeout(timeoutId)
}

const flush = (...args) => {
cancel()
fn(...args)
}

onUnmounted(() => {
cancel()
})

return {
debouncedFn,
cancel,
flush
}
}

// 节流函数
export function useThrottle(fn, delay = 300) {
let lastExecTime = 0
let timeoutId = null

const throttledFn = (...args) => {
const now = Date.now()

if (now - lastExecTime >= delay) {
lastExecTime = now
fn(...args)
} else {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => {
lastExecTime = Date.now()
fn(...args)
}, delay - (now - lastExecTime))
}
}

const cancel = () => {
clearTimeout(timeoutId)
}

onUnmounted(() => {
cancel()
})

return {
throttledFn,
cancel
}
}

// 窗口事件监听
export function useWindowEvent(event, handler, options = {}) {
const { passive = true, capture = false } = options

onMounted(() => {
window.addEventListener(event, handler, { passive, capture })
})

onUnmounted(() => {
window.removeEventListener(event, handler, { passive, capture })
})
}

// 元素事件监听
export function useEventListener(target, event, handler, options = {}) {
const { passive = true, capture = false } = options

const cleanup = () => {
if (target.value) {
target.value.removeEventListener(event, handler, { passive, capture })
}
}

watch(
target,
(newTarget, oldTarget) => {
if (oldTarget) {
oldTarget.removeEventListener(event, handler, { passive, capture })
}
if (newTarget) {
newTarget.addEventListener(event, handler, { passive, capture })
}
},
{ immediate: true }
)

onUnmounted(cleanup)

return cleanup
}

// 键盘快捷键
export function useKeyboard(keyMap) {
const pressedKeys = ref(new Set())

const handleKeyDown = (event) => {
pressedKeys.value.add(event.code)

// 检查快捷键组合
for (const [combination, handler] of Object.entries(keyMap)) {
const keys = combination.split('+')
const isMatch = keys.every(key => {
if (key === 'ctrl') return event.ctrlKey
if (key === 'alt') return event.altKey
if (key === 'shift') return event.shiftKey
if (key === 'meta') return event.metaKey
return pressedKeys.value.has(key)
})

if (isMatch) {
event.preventDefault()
handler(event)
break
}
}
}

const handleKeyUp = (event) => {
pressedKeys.value.delete(event.code)
}

useWindowEvent('keydown', handleKeyDown)
useWindowEvent('keyup', handleKeyUp)

return {
pressedKeys: readonly(pressedKeys)
}
}

// 使用示例
export function useSearchInput() {
const searchQuery = ref('')
const searchResults = ref([])
const isSearching = ref(false)

// 搜索函数
const performSearch = async (query) => {
if (!query.trim()) {
searchResults.value = []
return
}

isSearching.value = true
try {
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`)
const results = await response.json()
searchResults.value = results
} catch (error) {
console.error('搜索失败:', error)
searchResults.value = []
} finally {
isSearching.value = false
}
}

// 防抖搜索
const { debouncedFn: debouncedSearch } = useDebounce(performSearch, 300)

// 监听搜索查询变化
watch(searchQuery, (newQuery) => {
debouncedSearch(newQuery)
})

return {
searchQuery,
searchResults: readonly(searchResults),
isSearching: readonly(isSearching)
}
}

4. 状态持久化

本地存储组合式函数

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
// composables/useStorage.js
import { ref, watch, nextTick } from 'vue'

// 序列化器
const serializers = {
object: {
read: (value) => {
try {
return JSON.parse(value)
} catch {
return null
}
},
write: (value) => JSON.stringify(value)
},
boolean: {
read: (value) => value === 'true',
write: (value) => String(value)
},
number: {
read: (value) => Number(value),
write: (value) => String(value)
},
string: {
read: (value) => value,
write: (value) => String(value)
}
}

export function useStorage(key, defaultValue, storage = localStorage, options = {}) {
const {
serializer = 'object',
syncAcrossTabs = true,
writeDefaults = true
} = options

const serializer_ = typeof serializer === 'string'
? serializers[serializer]
: serializer

const storedValue = ref(defaultValue)

// 从存储中读取值
const read = () => {
try {
const item = storage.getItem(key)
if (item === null) {
if (writeDefaults && defaultValue !== null) {
write(defaultValue)
}
return defaultValue
}
return serializer_.read(item)
} catch (error) {
console.error(`读取存储失败 [${key}]:`, error)
return defaultValue
}
}

// 写入值到存储
const write = (value) => {
try {
if (value === null || value === undefined) {
storage.removeItem(key)
} else {
storage.setItem(key, serializer_.write(value))
}
} catch (error) {
console.error(`写入存储失败 [${key}]:`, error)
}
}

// 初始化值
storedValue.value = read()

// 监听值变化并同步到存储
watch(
storedValue,
(newValue) => {
write(newValue)
},
{ deep: true }
)

// 跨标签页同步
if (syncAcrossTabs && storage === localStorage) {
const handleStorageChange = (e) => {
if (e.key === key && e.newValue !== serializer_.write(storedValue.value)) {
storedValue.value = serializer_.read(e.newValue)
}
}

window.addEventListener('storage', handleStorageChange)

onUnmounted(() => {
window.removeEventListener('storage', handleStorageChange)
})
}

return storedValue
}

// 特定类型的存储函数
export const useLocalStorage = (key, defaultValue, options) =>
useStorage(key, defaultValue, localStorage, options)

export const useSessionStorage = (key, defaultValue, options) =>
useStorage(key, defaultValue, sessionStorage, options)

// 使用示例
export function useUserPreferences() {
const preferences = useLocalStorage('userPreferences', {
theme: 'light',
language: 'zh-CN',
sidebarCollapsed: false,
notifications: {
email: true,
push: false,
desktop: true
}
})

// 主题切换
const toggleTheme = () => {
preferences.value.theme = preferences.value.theme === 'light' ? 'dark' : 'light'
}

// 语言切换
const setLanguage = (lang) => {
preferences.value.language = lang
}

// 侧边栏切换
const toggleSidebar = () => {
preferences.value.sidebarCollapsed = !preferences.value.sidebarCollapsed
}

// 通知设置
const updateNotificationSettings = (settings) => {
Object.assign(preferences.value.notifications, settings)
}

return {
preferences: readonly(preferences),
toggleTheme,
setLanguage,
toggleSidebar,
updateNotificationSettings
}
}

组合式函数的设计原则

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
// ❌ 不好的设计:功能过于复杂
export function useUserManagement() {
// 用户数据
const users = ref([])
// 权限管理
const permissions = ref([])
// 通知系统
const notifications = ref([])
// 主题设置
const theme = ref('light')

// 太多不相关的功能混在一起
// ...
}

// ✅ 好的设计:职责分离
export function useUsers() {
const users = ref([])

const fetchUsers = async () => {
// 获取用户列表
}

const createUser = async (userData) => {
// 创建用户
}

return {
users: readonly(users),
fetchUsers,
createUser
}
}

export function usePermissions() {
const permissions = ref([])

const checkPermission = (permission) => {
return permissions.value.includes(permission)
}

return {
permissions: readonly(permissions),
checkPermission
}
}

export function useTheme() {
const theme = useLocalStorage('theme', 'light')

const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}

return {
theme: readonly(theme),
toggleTheme
}
}

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
// composables/useUserDashboard.js
import { useUsers } from './useUsers'
import { usePermissions } from './usePermissions'
import { useNotifications } from './useNotifications'

export function useUserDashboard() {
// 组合多个组合式函数
const { users, fetchUsers, createUser } = useUsers()
const { permissions, checkPermission } = usePermissions()
const { notifications, addNotification } = useNotifications()

// 组合逻辑
const canCreateUser = computed(() => checkPermission('user:create'))
const canDeleteUser = computed(() => checkPermission('user:delete'))

const createUserWithNotification = async (userData) => {
try {
await createUser(userData)
addNotification({
type: 'success',
message: '用户创建成功'
})
} catch (error) {
addNotification({
type: 'error',
message: '用户创建失败'
})
}
}

return {
users,
notifications,
canCreateUser,
canDeleteUser,
fetchUsers,
createUserWithNotification
}
}

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
// composables/useTypedApi.ts
import { ref, computed, Ref } from 'vue'

interface User {
id: number
name: string
email: string
role: 'admin' | 'user' | 'guest'
}

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

interface UseApiOptions {
immediate?: boolean
onError?: (error: Error) => void
onSuccess?: <T>(data: T) => void
}

export function useTypedApi<T>(
fetcher: () => Promise<ApiResponse<T>>,
options: UseApiOptions = {}
) {
const data: Ref<T | null> = ref(null)
const isLoading = ref(false)
const error: Ref<Error | null> = ref(null)

const execute = async (): Promise<T | null> => {
isLoading.value = true
error.value = null

try {
const response = await fetcher()
if (response.success) {
data.value = response.data
options.onSuccess?.(response.data)
return response.data
} else {
throw new Error(response.message)
}
} catch (err) {
const errorObj = err instanceof Error ? err : new Error(String(err))
error.value = errorObj
options.onError?.(errorObj)
return null
} finally {
isLoading.value = false
}
}

if (options.immediate) {
execute()
}

return {
data: readonly(data),
isLoading: readonly(isLoading),
error: readonly(error),
execute
}
}

// 使用示例
export function useUserApi(userId: number) {
const fetchUser = () =>
fetch(`/api/users/${userId}`).then(res => res.json()) as Promise<ApiResponse<User>>

return useTypedApi(fetchUser, {
immediate: true,
onError: (error) => console.error('获取用户失败:', error),
onSuccess: (user) => console.log('用户加载成功:', user.name)
})
}

性能优化技巧

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
// composables/useOptimizedList.js
import { shallowRef, triggerRef, computed } from 'vue'

export function useOptimizedList(initialItems = []) {
// 使用 shallowRef 避免深度响应式
const items = shallowRef([...initialItems])

// 计算属性仍然是响应式的
const itemCount = computed(() => items.value.length)
const isEmpty = computed(() => items.value.length === 0)

// 添加项目
const addItem = (item) => {
items.value.push(item)
triggerRef(items) // 手动触发更新
}

// 批量添加
const addItems = (newItems) => {
items.value.push(...newItems)
triggerRef(items)
}

// 移除项目
const removeItem = (index) => {
items.value.splice(index, 1)
triggerRef(items)
}

// 更新项目(创建新数组以触发响应式)
const updateItem = (index, newItem) => {
items.value = items.value.map((item, i) =>
i === index ? newItem : item
)
}

// 清空列表
const clear = () => {
items.value = []
triggerRef(items)
}

return {
items: readonly(items),
itemCount,
isEmpty,
addItem,
addItems,
removeItem,
updateItem,
clear
}
}

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
// composables/useExpensiveComputation.js
import { ref, computed, watchEffect } from 'vue'

export function useExpensiveComputation(source) {
const cache = new Map()
const result = ref(null)

// 使用 watchEffect 而不是 computed 来控制缓存
watchEffect(() => {
const key = JSON.stringify(source.value)

if (cache.has(key)) {
result.value = cache.get(key)
return
}

// 执行昂贵的计算
const computed = expensiveFunction(source.value)
cache.set(key, computed)
result.value = computed

// 限制缓存大小
if (cache.size > 100) {
const firstKey = cache.keys().next().value
cache.delete(firstKey)
}
})

const clearCache = () => {
cache.clear()
}

return {
result: readonly(result),
clearCache
}
}

function expensiveFunction(data) {
// 模拟昂贵的计算
console.log('执行昂贵计算...')
return data.map(item => ({ ...item, processed: true }))
}

总结

Composition API 为 Vue 3 带来了强大的逻辑复用和组织能力。通过合理设计组合式函数,我们可以:

  1. 提高代码复用性:将通用逻辑抽取为组合式函数
  2. 增强类型安全:结合 TypeScript 提供更好的开发体验
  3. 优化性能:使用合适的响应式 API 和缓存策略
  4. 改善可维护性:遵循单一职责和可组合性原则
  5. 增强测试性:组合式函数更容易进行单元测试

在实际项目中,应该根据具体需求选择合适的模式,避免过度抽象。记住,好的组合式函数应该是简单、可预测、易于理解和测试的。通过不断实践和优化,可以构建出高质量、可维护的 Vue 3 应用。

本站由 提供部署服务