- 发布于
Vue 3 Composition API深度指南:响应式系统与组合式函数实战
- 作者

- 姓名
- 全能波
- GitHub
- @weicracker
Vue 3 Composition API深度指南:响应式系统与组合式函数实战
Vue 3的Composition API为组件逻辑组织提供了更灵活的方式。本文将深入探讨Composition API的核心概念和实战应用技巧。
Composition API基础
响应式系统核心
<template>
<div class="counter-demo">
<h2>计数器演示</h2>
<div class="counter-display">
<span class="count">{{ count }}</span>
<span class="double-count">双倍: {{ doubleCount }}</span>
</div>
<div class="controls">
<button @click="increment">增加</button>
<button @click="decrement">减少</button>
<button @click="reset">重置</button>
</div>
<div class="info">
<p>点击次数: {{ clickCount }}</p>
<p>最后操作: {{ lastOperation }}</p>
<p>是否为偶数: {{ isEven ? '是' : '否' }}</p>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, watchEffect, reactive, toRefs } from 'vue'
// 基础响应式引用
const count = ref<number>(0)
const clickCount = ref<number>(0)
const lastOperation = ref<string>('无')
// 计算属性
const doubleCount = computed(() => count.value * 2)
const isEven = computed(() => count.value % 2 === 0)
// 响应式对象
const state = reactive({
history: [] as string[],
maxValue: 0,
minValue: 0
})
// 解构响应式对象
const { history, maxValue, minValue } = toRefs(state)
// 方法定义
const increment = () => {
count.value++
clickCount.value++
lastOperation.value = '增加'
state.history.push(`增加到 ${count.value}`)
if (count.value > state.maxValue) {
state.maxValue = count.value
}
}
const decrement = () => {
count.value--
clickCount.value++
lastOperation.value = '减少'
state.history.push(`减少到 ${count.value}`)
if (count.value < state.minValue) {
state.minValue = count.value
}
}
const reset = () => {
count.value = 0
clickCount.value++
lastOperation.value = '重置'
state.history.push('重置为 0')
}
// 监听器
watch(count, (newValue, oldValue) => {
console.log(`计数从 ${oldValue} 变为 ${newValue}`)
})
// 监听多个值
watch([count, clickCount], ([newCount, newClickCount], [oldCount, oldClickCount]) => {
console.log('多值监听:', {
count: { old: oldCount, new: newCount },
clicks: { old: oldClickCount, new: newClickCount }
})
})
// 立即执行的监听器
watchEffect(() => {
document.title = `计数器: ${count.value}`
})
// 深度监听响应式对象
watch(state, (newState) => {
console.log('状态变化:', newState)
}, { deep: true })
</script>
<style scoped>
.counter-demo {
max-width: 400px;
margin: 0 auto;
padding: 2rem;
border: 1px solid #ddd;
border-radius: 8px;
}
.counter-display {
display: flex;
justify-content: space-between;
margin: 1rem 0;
padding: 1rem;
background: #f5f5f5;
border-radius: 4px;
}
.count {
font-size: 2rem;
font-weight: bold;
color: #007bff;
}
.controls {
display: flex;
gap: 0.5rem;
margin: 1rem 0;
}
.controls button {
flex: 1;
padding: 0.5rem;
border: none;
border-radius: 4px;
background: #007bff;
color: white;
cursor: pointer;
}
.controls button:hover {
background: #0056b3;
}
.info {
margin-top: 1rem;
padding: 1rem;
background: #e9ecef;
border-radius: 4px;
}
.info p {
margin: 0.5rem 0;
}
</style>
响应式API详解
// composables/useReactivity.ts
import {
ref,
reactive,
computed,
watch,
watchEffect,
readonly,
shallowRef,
shallowReactive,
toRef,
toRefs,
unref,
isRef,
isReactive,
isReadonly,
markRaw,
nextTick
} from 'vue'
// 响应式API使用示例
export function useReactivityDemo() {
// 基础ref
const count = ref(0)
const message = ref('Hello Vue 3')
// 对象ref
const user = ref({
name: 'John',
age: 30,
profile: {
email: 'john@example.com',
avatar: 'avatar.jpg'
}
})
// reactive对象
const state = reactive({
loading: false,
error: null as string | null,
data: [] as any[],
filters: {
search: '',
category: 'all',
sortBy: 'name'
}
})
// 只读响应式
const readonlyState = readonly(state)
// 浅层响应式
const shallowState = shallowReactive({
surface: 'reactive',
nested: {
deep: 'not reactive'
}
})
// 浅层ref
const shallowCount = shallowRef({ count: 0 })
// 计算属性
const doubleCount = computed(() => count.value * 2)
// 可写计算属性
const fullName = computed({
get: () => `${user.value.name} (${user.value.age})`,
set: (value: string) => {
const [name, age] = value.split(' (')
user.value.name = name
user.value.age = parseInt(age.replace(')', ''))
}
})
// 监听器选项
const stopWatcher = watch(
count,
(newValue, oldValue) => {
console.log(`Count changed: ${oldValue} -> ${newValue}`)
},
{
immediate: true, // 立即执行
deep: true, // 深度监听
flush: 'post' // DOM更新后执行
}
)
// 监听多个源
watch(
[count, message],
([newCount, newMessage], [oldCount, oldMessage]) => {
console.log('Multiple sources changed')
}
)
// 监听响应式对象的特定属性
watch(
() => state.filters.search,
(newSearch) => {
console.log('Search filter changed:', newSearch)
}
)
// watchEffect自动收集依赖
const stopEffect = watchEffect(() => {
console.log(`Current count: ${count.value}, message: ${message.value}`)
})
// 异步watchEffect
watchEffect(async (onInvalidate) => {
const response = await fetch(`/api/data?count=${count.value}`)
// 清理函数
onInvalidate(() => {
console.log('Effect invalidated')
})
const data = await response.json()
state.data = data
})
// toRef和toRefs
const searchRef = toRef(state.filters, 'search')
const filtersRefs = toRefs(state.filters)
// 工具函数
const increment = () => {
count.value++
}
const updateUser = (updates: Partial<typeof user.value>) => {
Object.assign(user.value, updates)
}
const toggleLoading = () => {
state.loading = !state.loading
}
// 检查响应式类型
const checkReactivity = () => {
console.log('isRef(count):', isRef(count))
console.log('isReactive(state):', isReactive(state))
console.log('isReadonly(readonlyState):', isReadonly(readonlyState))
}
// 标记非响应式
const nonReactiveData = markRaw({
heavyData: new Array(1000000).fill(0)
})
// 清理函数
const cleanup = () => {
stopWatcher()
stopEffect()
}
return {
// 响应式数据
count,
message,
user,
state,
readonlyState,
shallowState,
shallowCount,
// 计算属性
doubleCount,
fullName,
// 解构的refs
searchRef,
...filtersRefs,
// 方法
increment,
updateUser,
toggleLoading,
checkReactivity,
cleanup,
// 非响应式数据
nonReactiveData
}
}
// 响应式工具函数
export function useReactiveUtils() {
// 安全的unref
const safeUnref = <T>(val: T | Ref<T>): T => {
return unref(val)
}
// 创建可选的响应式引用
const maybeRef = <T>(val: T | Ref<T>): Ref<T> => {
return isRef(val) ? val : ref(val)
}
// 创建切换状态的ref
const useToggle = (initialValue = false) => {
const state = ref(initialValue)
const toggle = (value?: boolean) => {
state.value = typeof value === 'boolean' ? value : !state.value
}
return [state, toggle] as const
}
// 创建计数器
const useCounter = (initialValue = 0) => {
const count = ref(initialValue)
const increment = (delta = 1) => {
count.value += delta
}
const decrement = (delta = 1) => {
count.value -= delta
}
const reset = () => {
count.value = initialValue
}
return {
count: readonly(count),
increment,
decrement,
reset
}
}
// 创建异步状态管理
const useAsyncState = <T>(
promise: Promise<T>,
initialState: T,
options: {
resetOnExecute?: boolean
shallow?: boolean
} = {}
) => {
const { resetOnExecute = true, shallow = true } = options
const state = shallow ? shallowRef(initialState) : ref(initialState)
const isReady = ref(false)
const isLoading = ref(false)
const error = ref<Error | null>(null)
const execute = async () => {
error.value = null
isReady.value = false
isLoading.value = true
if (resetOnExecute) {
state.value = initialState
}
try {
const data = await promise
state.value = data
isReady.value = true
} catch (err) {
error.value = err as Error
} finally {
isLoading.value = false
}
}
execute()
return {
state: readonly(state),
isReady: readonly(isReady),
isLoading: readonly(isLoading),
error: readonly(error),
execute
}
}
return {
safeUnref,
maybeRef,
useToggle,
useCounter,
useAsyncState
}
}
组合式函数实战
自定义Hooks开发
// composables/useLocalStorage.ts
import { ref, watch, Ref } from 'vue'
export function useLocalStorage<T>(
key: string,
defaultValue: T,
options: {
serializer?: {
read: (value: string) => T
write: (value: T) => string
}
syncAcrossTabs?: boolean
} = {}
): [Ref<T>, (value: T) => void, () => void] {
const {
serializer = {
read: JSON.parse,
write: JSON.stringify
},
syncAcrossTabs = true
} = options
// 读取初始值
const storedValue = localStorage.getItem(key)
const initialValue = storedValue !== null
? serializer.read(storedValue)
: defaultValue
const state = ref<T>(initialValue)
// 写入localStorage
const setValue = (value: T) => {
try {
state.value = value
localStorage.setItem(key, serializer.write(value))
} catch (error) {
console.error(`Error setting localStorage key "${key}":`, error)
}
}
// 删除localStorage
const removeValue = () => {
try {
localStorage.removeItem(key)
state.value = defaultValue
} catch (error) {
console.error(`Error removing localStorage key "${key}":`, error)
}
}
// 监听值变化
watch(
state,
(newValue) => {
setValue(newValue)
},
{ deep: true }
)
// 跨标签页同步
if (syncAcrossTabs) {
window.addEventListener('storage', (e) => {
if (e.key === key && e.newValue !== null) {
try {
state.value = serializer.read(e.newValue)
} catch (error) {
console.error(`Error syncing localStorage key "${key}":`, error)
}
}
})
}
return [state, setValue, removeValue]
}
// composables/useFetch.ts
import { ref, reactive, toRefs } from 'vue'
interface UseFetchOptions {
immediate?: boolean
refetch?: boolean
initialData?: any
timeout?: number
beforeFetch?: (ctx: { url: string; options: RequestInit; cancel: () => void }) => Promise<void> | void
afterFetch?: (ctx: { data: any; response: Response }) => any
onFetchError?: (ctx: { data: any; error: Error; response?: Response }) => void
}
export function useFetch<T = any>(
url: string,
options: UseFetchOptions = {}
) {
const {
immediate = true,
refetch = false,
initialData = null,
timeout = 0,
beforeFetch,
afterFetch,
onFetchError
} = options
const state = reactive({
data: initialData as T | null,
error: null as Error | null,
isFetching: false,
canAbort: false,
statusCode: null as number | null,
response: null as Response | null
})
let controller: AbortController | null = null
const abort = () => {
if (controller) {
controller.abort()
controller = null
state.canAbort = false
}
}
const execute = async (throwOnFailed = false) => {
abort()
controller = new AbortController()
state.isFetching = true
state.canAbort = true
state.error = null
const fetchOptions: RequestInit = {
signal: controller.signal
}
// 超时处理
if (timeout > 0) {
setTimeout(() => {
if (controller) {
controller.abort()
}
}, timeout)
}
try {
// beforeFetch钩子
if (beforeFetch) {
await beforeFetch({
url,
options: fetchOptions,
cancel: abort
})
}
const response = await fetch(url, fetchOptions)
state.response = response
state.statusCode = response.status
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
let data = await response.json()
// afterFetch钩子
if (afterFetch) {
data = afterFetch({ data, response }) || data
}
state.data = data
return data
} catch (error) {
const fetchError = error as Error
state.error = fetchError
// onFetchError钩子
if (onFetchError) {
onFetchError({
data: state.data,
error: fetchError,
response: state.response || undefined
})
}
if (throwOnFailed) {
throw fetchError
}
return null
} finally {
state.isFetching = false
state.canAbort = false
controller = null
}
}
// 立即执行
if (immediate) {
execute()
}
return {
...toRefs(state),
execute,
abort,
refetch: () => execute()
}
}
// composables/useIntersectionObserver.ts
import { ref, watch, unref, type Ref } from 'vue'
export function useIntersectionObserver(
target: Ref<Element | null> | Element | null,
callback: IntersectionObserverCallback,
options: IntersectionObserverInit = {}
) {
const isSupported = window && 'IntersectionObserver' in window
const isIntersecting = ref(false)
const isActive = ref(false)
let observer: IntersectionObserver | null = null
const cleanup = () => {
if (observer) {
observer.disconnect()
observer = null
isActive.value = false
}
}
const start = () => {
if (!isSupported) return
cleanup()
const element = unref(target)
if (!element) return
observer = new IntersectionObserver(
(entries) => {
isIntersecting.value = entries[0].isIntersecting
callback(entries, observer!)
},
options
)
observer.observe(element)
isActive.value = true
}
const stop = cleanup
// 监听target变化
watch(
() => unref(target),
(newTarget) => {
if (newTarget) {
start()
} else {
stop()
}
},
{ immediate: true }
)
return {
isSupported,
isIntersecting,
isActive,
start,
stop
}
}
总结
Vue 3 Composition API的核心要点:
- 响应式系统:ref、reactive、computed、watch等API的深度应用
- 组合式函数:逻辑复用、状态管理、异步处理的最佳实践
- 生命周期钩子:组件生命周期的精确控制和优化
- 性能优化:防抖节流、虚拟滚动、内存管理等技巧
- TypeScript集成:类型安全的组合式函数开发
Composition API为Vue 3带来了更强大的逻辑组织能力,通过合理使用这些特性,可以构建出更加灵活、可维护的Vue应用。