Appearance
第3.6节:响应式工具:toRefs、customRef 等 API 的实现与应用场景
Vue.js 3的响应式系统不仅提供了基础的ref和reactiveAPI,还提供了一系列强大的工具函数,用于在不同场景下灵活地处理响应式数据。这些工具API包括toRefs、customRef、triggerRef等,它们在组合式API的使用中发挥着重要作用,特别是在数据传递、第三方库集成和性能优化方面。本节将深入分析这些工具API的实现原理和应用场景。
3.6.1 响应式工具API概览
核心工具函数分类
Vue 3的响应式工具API可以分为以下几类:
typescript
// 1. 类型判断工具
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
export function isReactive(value: unknown): boolean
export function isReadonly(value: unknown): boolean
export function isProxy(value: unknown): boolean
// 2. 值提取工具
export function unref<T>(ref: MaybeRef<T> | ComputedRef<T>): T
export function toValue<T>(source: MaybeRefOrGetter<T>): T
// 3. 转换工具
export function toRefs<T extends object>(object: T): ToRefs<T>
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K,
): ToRef<T[K]>
// 4. 自定义ref工具
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T>
export function triggerRef(ref: Ref): void
// 5. 代理工具
export function proxyRefs<T extends object>(
objectWithRefs: T,
): ShallowUnwrapRef<T>类型定义体系
typescript
// 基础类型
export type MaybeRef<T = any> =
| T
| Ref<T>
| ShallowRef<T>
| WritableComputedRef<T>
export type MaybeRefOrGetter<T = any> = MaybeRef<T> | ComputedRef<T> | (() => T)
// 转换类型
export type ToRefs<T = any> = {
[K in keyof T]: ToRef<T[K]>
}
export type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>
// 自定义ref工厂类型
export type CustomRefFactory<T> = (
track: () => void,
trigger: () => void,
) => {
get: () => T
set: (value: T) => void
}3.6.2 toRefs:对象属性转ref的实现
基本用法与特点
toRefs函数将响应式对象的每个属性转换为独立的ref,这在组合式API中非常有用:
typescript
// 基本用法
const state = reactive({
name: 'Vue',
version: '3.0',
features: ['Composition API', 'Teleport']
})
const stateRefs = toRefs(state)
// stateRefs.name 是 Ref<string>
// stateRefs.version 是 Ref<string>
// stateRefs.features 是 Ref<string[]>
// 解构使用
const { name, version } = toRefs(state)
console.log(name.value) // 'Vue'
console.log(version.value) // '3.0'toRefs的核心实现
typescript
/**
* 将响应式对象转换为普通对象,其中结果对象的每个属性都是
* 指向原始对象相应属性的ref。每个单独的ref都是使用toRef创建的。
*/
export function toRefs<T extends object>(object: T): ToRefs<T> {
// 开发环境下的类型检查
if (__DEV__ && !isProxy(object)) {
warn(`toRefs() expects a reactive object but received a plain one.`)
}
// 根据对象类型创建返回值容器
const ret: any = isArray(object) ? new Array(object.length) : {}
// 遍历对象的所有属性
for (const key in object) {
ret[key] = propertyToRef(object, key)
}
return ret
}propertyToRef:属性转ref的核心逻辑
typescript
function propertyToRef(
source: Record<string, any>,
key: string,
defaultValue?: unknown,
) {
const val = source[key]
// 如果属性值已经是ref,直接返回
return isRef(val)
? val
: (new ObjectRefImpl(source, key, defaultValue) as any)
}ObjectRefImpl:对象属性ref的实现
typescript
class ObjectRefImpl<T extends object, K extends keyof T> {
public readonly [ReactiveFlags.IS_REF] = true
public _value: T[K] = undefined!
constructor(
private readonly _object: T,
private readonly _key: K,
private readonly _defaultValue?: T[K],
) {}
get value() {
const val = this._object[this._key]
return (this._value = val === undefined ? this._defaultValue! : val)
}
set value(newVal) {
this._object[this._key] = newVal
}
get dep(): Dep | undefined {
// 获取原始对象对应属性的依赖
return getDepFromReactive(toRaw(this._object), this._key)
}
}toRefs的应用场景
1. 组合函数的返回值
typescript
// 组合函数
function useCounter(initialValue = 0) {
const state = reactive({
count: initialValue,
doubled: computed(() => state.count * 2),
increment: () => state.count++,
decrement: () => state.count--,
})
// 使用toRefs确保解构后仍保持响应性
return toRefs(state)
}
// 使用组合函数
const { count, doubled, increment, decrement } = useCounter(10)
// count和doubled保持响应性
watchEffect(() => {
console.log(`Count: ${count.value}, Doubled: ${doubled.value}`)
})2. Props的解构
typescript
// 在setup函数中解构props
export default {
props: {
user: Object,
settings: Object
},
setup(props) {
// 直接解构会失去响应性
// const { user, settings } = props // ❌ 失去响应性
// 使用toRefs保持响应性
const { user, settings } = toRefs(props) // ✅ 保持响应性
watchEffect(() => {
console.log('User changed:', user.value)
})
return { user, settings }
}
}3. 状态管理中的模块化
typescript
// 状态模块
function createUserModule() {
const state = reactive({
currentUser: null,
isLoggedIn: false,
permissions: []
})
const actions = {
login(user) {
state.currentUser = user
state.isLoggedIn = true
},
logout() {
state.currentUser = null
state.isLoggedIn = false
state.permissions = []
}
}
return {
...toRefs(state),
...actions
}
}
// 使用模块
const { currentUser, isLoggedIn, login, logout } = createUserModule()3.6.3 toRef:单个属性转ref
多重函数重载
toRef函数支持多种使用方式:
typescript
// 1. 将值转换为ref
export function toRef<T>(
value: T,
): T extends () => infer R
? Readonly<Ref<R>>
: T extends Ref
? T
: Ref<UnwrapRef<T>>
// 2. 将对象属性转换为ref
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K,
): ToRef<T[K]>
// 3. 带默认值的属性转ref
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K,
defaultValue: T[K],
): ToRef<Exclude<T[K], undefined>>toRef的实现逻辑
typescript
export function toRef(
source: Record<string, any> | MaybeRef,
key?: string,
defaultValue?: unknown,
): Ref {
if (isRef(source)) {
// 如果source已经是ref,直接返回
return source
} else if (isFunction(source)) {
// 如果source是函数,创建只读的getter ref
return new GetterRefImpl(source) as any
} else if (isObject(source) && arguments.length > 1) {
// 如果是对象且提供了key,创建属性ref
return propertyToRef(source, key!, defaultValue)
} else {
// 其他情况,创建普通ref
return ref(source)
}
}GetterRefImpl:只读getter ref
typescript
class GetterRefImpl<T> {
public readonly [ReactiveFlags.IS_REF] = true
public readonly [ReactiveFlags.IS_READONLY] = true
public _value: T = undefined!
constructor(private readonly _getter: () => T) {}
get value() {
return (this._value = this._getter())
}
}toRef的应用场景
1. 创建计算属性的别名
typescript
const user = reactive({
firstName: 'John',
lastName: 'Doe'
})
// 创建计算属性的ref别名
const fullName = toRef(() => `${user.firstName} ${user.lastName}`)
console.log(fullName.value) // 'John Doe'
// fullName是只读的
// fullName.value = 'Jane Smith' // 错误:只读属性2. 可选属性的安全访问
typescript
const config = reactive({
api: {
baseUrl: 'https://api.example.com',
timeout: 5000
}
})
// 安全地创建可能不存在的属性的ref
const apiTimeout = toRef(config.api, 'timeout', 3000)
const retryCount = toRef(config.api, 'retryCount', 3) // 带默认值
console.log(apiTimeout.value) // 5000
console.log(retryCount.value) // 3 (默认值)3. 动态属性访问
typescript
function createPropertyRef<T extends object, K extends keyof T>(
obj: T,
key: K
) {
return toRef(obj, key)
}
const settings = reactive({
theme: 'dark',
language: 'en',
notifications: true
})
// 动态创建属性ref
const themeRef = createPropertyRef(settings, 'theme')
const langRef = createPropertyRef(settings, 'language')
// 这些ref与原对象保持同步
watchEffect(() => {
console.log(`Theme: ${themeRef.value}, Language: ${langRef.value}`)
})3.6.4 customRef:自定义ref实现
customRef的设计理念
customRef允许开发者完全控制ref的依赖追踪和更新触发,这为创建高度定制化的响应式数据提供了可能:
typescript
export type CustomRefFactory<T> = (
track: () => void, // 手动触发依赖收集
trigger: () => void, // 手动触发更新
) => {
get: () => T, // getter函数
set: (value: T) => void // setter函数
}
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
return new CustomRefImpl(factory) as any
}CustomRefImpl的实现
typescript
class CustomRefImpl<T> {
public dep: Dep
private readonly _get: ReturnType<CustomRefFactory<T>>['get']
private readonly _set: ReturnType<CustomRefFactory<T>>['set']
public readonly [ReactiveFlags.IS_REF] = true
public _value: T = undefined!
constructor(factory: CustomRefFactory<T>) {
const dep = (this.dep = new Dep())
// 将dep的track和trigger方法传递给工厂函数
const { get, set } = factory(dep.track.bind(dep), dep.trigger.bind(dep))
this._get = get
this._set = set
}
get value() {
return (this._value = this._get())
}
set value(newVal) {
this._set(newVal)
}
}customRef的应用场景
1. 防抖ref
typescript
function useDebouncedRef<T>(value: T, delay = 200) {
let timeout: number
return customRef<T>((track, trigger) => {
return {
get() {
track() // 收集依赖
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger() // 触发更新
}, delay)
}
}
})
}
// 使用防抖ref
const searchQuery = useDebouncedRef('', 300)
// 监听搜索查询变化
watchEffect(() => {
if (searchQuery.value) {
console.log('Searching for:', searchQuery.value)
// 执行搜索逻辑
}
})
// 快速输入不会触发多次搜索
searchQuery.value = 'v'
searchQuery.value = 'vu'
searchQuery.value = 'vue'
// 只有最后一次会在300ms后触发搜索2. 节流ref
typescript
function useThrottledRef<T>(value: T, limit = 100) {
let inThrottle = false
return customRef<T>((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
if (!inThrottle) {
value = newValue
trigger()
inThrottle = true
setTimeout(() => {
inThrottle = false
}, limit)
}
}
}
})
}
// 使用节流ref
const scrollPosition = useThrottledRef(0, 16) // 约60fps
window.addEventListener('scroll', () => {
scrollPosition.value = window.scrollY
})3. 本地存储同步ref
typescript
function useLocalStorageRef<T>(
key: string,
defaultValue: T,
serializer = JSON
) {
const storedValue = localStorage.getItem(key)
let value = storedValue ? serializer.parse(storedValue) : defaultValue
return customRef<T>((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
value = newValue
localStorage.setItem(key, serializer.stringify(newValue))
trigger()
}
}
})
}
// 使用本地存储ref
const userPreferences = useLocalStorageRef('user-prefs', {
theme: 'light',
language: 'en'
})
// 自动同步到localStorage
userPreferences.value.theme = 'dark'4. 异步数据ref
typescript
function useAsyncRef<T>(asyncFn: () => Promise<T>, initialValue: T) {
let value = initialValue
let loading = false
let error: Error | null = null
const loadData = async () => {
loading = true
error = null
try {
const result = await asyncFn()
value = result
loading = false
} catch (err) {
error = err as Error
loading = false
}
}
return customRef<{
data: T
loading: boolean
error: Error | null
reload: () => Promise<void>
}>((track, trigger) => {
return {
get() {
track()
return {
data: value,
loading,
error,
reload: async () => {
await loadData()
trigger()
}
}
},
set() {
// 异步ref通常是只读的
console.warn('Cannot set async ref directly')
}
}
})
}
// 使用异步数据ref
const userDataRef = useAsyncRef(
() => fetch('/api/user').then(res => res.json()),
null
)
// 自动加载数据
userDataRef.value.reload()
// 监听数据变化
watchEffect(() => {
const { data, loading, error } = userDataRef.value
if (loading) {
console.log('Loading user data...')
} else if (error) {
console.error('Failed to load user data:', error)
} else {
console.log('User data:', data)
}
})3.6.5 triggerRef:手动触发ref更新
triggerRef的实现
typescript
/**
* 手动触发与浅层ref相关的副作用。
* 这通常在对浅层ref的内部值进行深度变更后使用。
*/
export function triggerRef(ref: Ref): void {
// ref可能是ObjectRefImpl的实例
if ((ref as unknown as RefImpl).dep) {
if (__DEV__) {
;(ref as unknown as RefImpl).dep.trigger({
target: ref,
type: TriggerOpTypes.SET,
key: 'value',
newValue: (ref as unknown as RefImpl)._value,
})
} else {
;(ref as unknown as RefImpl).dep.trigger()
}
}
}triggerRef的应用场景
1. 浅层ref的深度变更
typescript
const shallowState = shallowRef({
nested: {
count: 0,
items: []
}
})
// 监听状态变化
watchEffect(() => {
console.log('State changed:', shallowState.value.nested.count)
})
// 直接修改嵌套属性不会触发更新
shallowState.value.nested.count++ // 不会触发watchEffect
// 手动触发更新
triggerRef(shallowState) // 现在会触发watchEffect2. 性能优化的批量更新
typescript
function useBatchUpdates<T extends object>(initialState: T) {
const state = shallowRef(initialState)
let pendingUpdate = false
const batchUpdate = (updater: (state: T) => void) => {
updater(state.value)
if (!pendingUpdate) {
pendingUpdate = true
nextTick(() => {
triggerRef(state)
pendingUpdate = false
})
}
}
return {
state: readonly(state),
batchUpdate
}
}
// 使用批量更新
const { state, batchUpdate } = useBatchUpdates({
items: [],
total: 0,
selected: []
})
// 批量修改多个属性,只触发一次更新
batchUpdate(state => {
state.items.push(...newItems)
state.total = state.items.length
state.selected = []
})3. 第三方库集成
typescript
// 集成第三方状态管理库
function createStoreRef<T>(store: ThirdPartyStore<T>) {
const stateRef = shallowRef(store.getState())
// 监听第三方store的变化
store.subscribe(() => {
stateRef.value = store.getState()
triggerRef(stateRef) // 手动触发Vue的响应式更新
})
return {
state: readonly(stateRef),
dispatch: store.dispatch.bind(store)
}
}3.6.6 类型判断工具:isRef、isReactive等
类型判断函数的实现
typescript
// 判断是否为ref
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
export function isRef(r: any): r is Ref {
return r ? r[ReactiveFlags.IS_REF] === true : false
}
// 判断是否为响应式对象(来自reactive.ts)
export function isReactive(value: unknown): boolean {
if (isReadonly(value)) {
return isReactive((value as Target)[ReactiveFlags.RAW])
}
return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}
// 判断是否为只读对象
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}
// 判断是否为代理对象
export function isProxy(value: unknown): boolean {
return isReactive(value) || isReadonly(value)
}
// 判断是否为浅层响应式
export function isShallow(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_SHALLOW])
}类型判断的应用场景
1. 条件响应式处理
typescript
function processValue<T>(value: T | Ref<T>): T {
if (isRef(value)) {
return value.value
}
return value
}
// 通用的响应式数据处理
function normalizeReactiveData(data: unknown) {
if (isRef(data)) {
return { type: 'ref', value: data.value }
} else if (isReactive(data)) {
return { type: 'reactive', value: toRaw(data) }
} else if (isReadonly(data)) {
return { type: 'readonly', value: toRaw(data) }
} else {
return { type: 'plain', value: data }
}
}2. 开发工具和调试
typescript
// 开发环境下的响应式数据检查器
function inspectReactiveData(data: unknown, name: string) {
if (__DEV__) {
const info = {
name,
isRef: isRef(data),
isReactive: isReactive(data),
isReadonly: isReadonly(data),
isShallow: isShallow(data),
isProxy: isProxy(data)
}
console.group(`Reactive Data Inspector: ${name}`)
console.table(info)
console.log('Raw value:', toRaw(data))
console.groupEnd()
}
}
// 使用检查器
const state = reactive({ count: 0 })
const countRef = ref(10)
inspectReactiveData(state, 'state')
inspectReactiveData(countRef, 'countRef')3. 类型安全的工具函数
typescript
// 类型安全的深度克隆
function deepCloneReactive<T>(source: T): T {
if (isRef(source)) {
return ref(deepCloneReactive(source.value)) as T
} else if (isReactive(source)) {
const raw = toRaw(source)
const cloned = Array.isArray(raw) ? [] : {}
for (const key in raw) {
cloned[key] = deepCloneReactive(raw[key])
}
return reactive(cloned) as T
} else if (isReadonly(source)) {
const raw = toRaw(source)
const cloned = deepCloneReactive(raw)
return readonly(cloned) as T
} else {
return source
}
}3.6.7 值提取工具:unref和toValue
unref:ref值提取
typescript
/**
* 如果参数是ref,返回内部值,否则返回参数本身。
* 这是 `val = isRef(val) ? val.value : val` 的语法糖。
*/
export function unref<T>(ref: MaybeRef<T> | ComputedRef<T>): T {
return isRef(ref) ? ref.value : ref
}toValue:通用值提取
typescript
/**
* 将值/ref/getter标准化为值。
* 这类似于unref,但它也会标准化getter。
* 如果参数是getter,它将被调用并返回其返回值。
*/
export function toValue<T>(source: MaybeRefOrGetter<T>): T {
return isFunction(source) ? source() : unref(source)
}值提取工具的应用
1. 通用工具函数
typescript
// 通用的格式化函数
function formatCurrency(
amount: MaybeRefOrGetter<number>,
currency: MaybeRefOrGetter<string> = 'USD'
) {
const amountValue = toValue(amount)
const currencyValue = toValue(currency)
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currencyValue
}).format(amountValue)
}
// 灵活使用
const price = ref(99.99)
const currency = ref('EUR')
// 支持多种参数类型
console.log(formatCurrency(99.99, 'USD')) // 直接值
console.log(formatCurrency(price, currency)) // ref
console.log(formatCurrency(() => price.value * 1.2, 'GBP')) // getter2. 条件计算
typescript
function useConditionalComputed<T>(
condition: MaybeRefOrGetter<boolean>,
whenTrue: MaybeRefOrGetter<T>,
whenFalse: MaybeRefOrGetter<T>
) {
return computed(() => {
return toValue(condition) ? toValue(whenTrue) : toValue(whenFalse)
})
}
// 使用示例
const isLoggedIn = ref(false)
const userName = ref('Guest')
const adminName = ref('Admin')
const displayName = useConditionalComputed(
isLoggedIn,
() => `Welcome, ${userName.value}!`,
'Please log in'
)3.6.8 proxyRefs:自动解包代理
proxyRefs的实现
typescript
const shallowUnwrapHandlers: ProxyHandler<any> = {
get: (target, key, receiver) =>
key === ReactiveFlags.RAW
? target
: unref(Reflect.get(target, key, receiver)),
set: (target, key, value, receiver) => {
const oldValue = target[key]
if (isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
} else {
return Reflect.set(target, key, value, receiver)
}
},
}
/**
* 返回给定对象的代理,该代理浅层解包属性中的ref。
* 如果对象已经是响应式的,则按原样返回。
* 如果不是,则创建新的响应式代理。
*/
export function proxyRefs<T extends object>(
objectWithRefs: T,
): ShallowUnwrapRef<T> {
return isReactive(objectWithRefs)
? (objectWithRefs as ShallowUnwrapRef<T>)
: new Proxy(objectWithRefs, shallowUnwrapHandlers)
}proxyRefs的应用场景
1. 组合函数的便捷返回
typescript
function useCounter(initialValue = 0) {
const count = ref(initialValue)
const doubled = computed(() => count.value * 2)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue
// 使用proxyRefs自动解包
return proxyRefs({
count,
doubled,
increment,
decrement,
reset
})
}
// 使用时无需.value
const counter = useCounter(10)
console.log(counter.count) // 10,自动解包
console.log(counter.doubled) // 20,自动解包
counter.count = 20 // 自动设置ref的value
counter.increment() // 调用函数2. 模板中的便捷访问
typescript
// 在setup函数中
export default {
setup() {
const user = ref({ name: 'John', age: 30 })
const isLoading = ref(false)
const error = ref(null)
// 返回proxyRefs,模板中无需.value
return proxyRefs({
user,
isLoading,
error,
async loadUser() {
isLoading.value = true
try {
const userData = await fetchUser()
user.value = userData
} catch (err) {
error.value = err
} finally {
isLoading.value = false
}
}
})
}
}vue
<template>
<!-- 模板中直接使用,无需.value -->
<div v-if="isLoading">Loading...</div>
<div v-else-if="error">Error: {{ error.message }}</div>
<div v-else>
<h1>{{ user.name }}</h1>
<p>Age: {{ user.age }}</p>
<button @click="loadUser">Reload</button>
</div>
</template>3.6.9 高级应用场景
1. 响应式数据的序列化和反序列化
typescript
// 响应式数据序列化器
class ReactiveSerializer {
static serialize(data: any): string {
const normalized = this.normalizeForSerialization(data)
return JSON.stringify(normalized)
}
static deserialize<T>(json: string): T {
const data = JSON.parse(json)
return this.restoreReactivity(data)
}
private static normalizeForSerialization(data: any): any {
if (isRef(data)) {
return {
__type: 'ref',
value: this.normalizeForSerialization(data.value)
}
} else if (isReactive(data)) {
const raw = toRaw(data)
const normalized = {}
for (const key in raw) {
normalized[key] = this.normalizeForSerialization(raw[key])
}
return {
__type: 'reactive',
value: normalized
}
} else if (Array.isArray(data)) {
return data.map(item => this.normalizeForSerialization(item))
} else if (data && typeof data === 'object') {
const normalized = {}
for (const key in data) {
normalized[key] = this.normalizeForSerialization(data[key])
}
return normalized
}
return data
}
private static restoreReactivity(data: any): any {
if (data && typeof data === 'object' && data.__type) {
switch (data.__type) {
case 'ref':
return ref(this.restoreReactivity(data.value))
case 'reactive':
return reactive(this.restoreReactivity(data.value))
}
} else if (Array.isArray(data)) {
return data.map(item => this.restoreReactivity(item))
} else if (data && typeof data === 'object') {
const restored = {}
for (const key in data) {
restored[key] = this.restoreReactivity(data[key])
}
return restored
}
return data
}
}
// 使用序列化器
const originalState = reactive({
user: ref({ name: 'John', age: 30 }),
settings: {
theme: ref('dark'),
notifications: ref(true)
}
})
// 序列化
const serialized = ReactiveSerializer.serialize(originalState)
console.log('Serialized:', serialized)
// 反序列化
const restored = ReactiveSerializer.deserialize(serialized)
console.log('Restored:', restored)
// 验证响应性
watchEffect(() => {
console.log('User name:', restored.user.name)
})
restored.user.name = 'Jane' // 触发watchEffect2. 响应式数据的时间旅行调试
typescript
// 时间旅行调试器
class ReactiveTimeTravel<T> {
private history: Array<{ timestamp: number; state: T }> = []
private currentIndex = -1
private maxHistorySize = 50
constructor(private state: T) {
this.saveState()
this.setupWatchers()
}
private setupWatchers() {
// 深度监听状态变化
watchEffect(() => {
const serialized = JSON.stringify(toRaw(this.state))
// 防抖保存状态
this.debouncedSaveState()
})
}
private debouncedSaveState = debounce(() => {
this.saveState()
}, 100)
private saveState() {
const snapshot = {
timestamp: Date.now(),
state: JSON.parse(JSON.stringify(toRaw(this.state)))
}
// 移除当前位置之后的历史记录
this.history = this.history.slice(0, this.currentIndex + 1)
// 添加新状态
this.history.push(snapshot)
this.currentIndex = this.history.length - 1
// 限制历史记录大小
if (this.history.length > this.maxHistorySize) {
this.history.shift()
this.currentIndex--
}
}
canUndo(): boolean {
return this.currentIndex > 0
}
canRedo(): boolean {
return this.currentIndex < this.history.length - 1
}
undo(): boolean {
if (this.canUndo()) {
this.currentIndex--
this.restoreState(this.history[this.currentIndex].state)
return true
}
return false
}
redo(): boolean {
if (this.canRedo()) {
this.currentIndex++
this.restoreState(this.history[this.currentIndex].state)
return true
}
return false
}
private restoreState(snapshot: T) {
// 递归恢复状态
this.deepAssign(this.state, snapshot)
}
private deepAssign(target: any, source: any) {
for (const key in source) {
if (isRef(target[key])) {
target[key].value = source[key]
} else if (isReactive(target[key]) && typeof source[key] === 'object') {
this.deepAssign(target[key], source[key])
} else {
target[key] = source[key]
}
}
}
getHistory() {
return this.history.map((item, index) => ({
...item,
isCurrent: index === this.currentIndex
}))
}
}
// 使用时间旅行调试器
const appState = reactive({
todos: [
{ id: 1, text: 'Learn Vue 3', completed: false },
{ id: 2, text: 'Build an app', completed: false }
],
filter: 'all'
})
const timeTravel = new ReactiveTimeTravel(appState)
// 模拟状态变化
appState.todos[0].completed = true
appState.filter = 'completed'
appState.todos.push({ id: 3, text: 'Deploy app', completed: false })
// 撤销操作
console.log('Can undo:', timeTravel.canUndo())
timeTravel.undo() // 撤销添加todo
timeTravel.undo() // 撤销filter变化
timeTravel.undo() // 撤销完成todo
// 重做操作
timeTravel.redo() // 重做完成todo
// 查看历史记录
console.log('History:', timeTravel.getHistory())3. 响应式数据的性能监控
typescript
// 响应式性能监控器
class ReactivePerformanceMonitor {
private static instance: ReactivePerformanceMonitor
private metrics = reactive({
refAccess: 0,
refMutation: 0,
reactiveAccess: 0,
reactiveMutation: 0,
computedEvaluation: 0,
watcherExecution: 0
})
private accessTimes: number[] = []
private mutationTimes: number[] = []
static getInstance() {
if (!this.instance) {
this.instance = new ReactivePerformanceMonitor()
}
return this.instance
}
// 包装ref以监控性能
wrapRef<T>(value: T): Ref<T> {
const originalRef = ref(value)
return customRef<T>((track, trigger) => {
return {
get() {
const start = performance.now()
track()
const result = originalRef.value
const end = performance.now()
this.recordAccess('ref', end - start)
return result
},
set(newValue) {
const start = performance.now()
originalRef.value = newValue
trigger()
const end = performance.now()
this.recordMutation('ref', end - start)
}
}
})
}
// 包装reactive以监控性能
wrapReactive<T extends object>(target: T): T {
const originalReactive = reactive(target)
return new Proxy(originalReactive, {
get(target, key, receiver) {
const start = performance.now()
const result = Reflect.get(target, key, receiver)
const end = performance.now()
this.recordAccess('reactive', end - start)
return result
},
set(target, key, value, receiver) {
const start = performance.now()
const result = Reflect.set(target, key, value, receiver)
const end = performance.now()
this.recordMutation('reactive', end - start)
return result
}
})
}
private recordAccess(type: 'ref' | 'reactive', time: number) {
this.metrics[`${type}Access`]++
this.accessTimes.push(time)
// 保持最近1000次记录
if (this.accessTimes.length > 1000) {
this.accessTimes.shift()
}
}
private recordMutation(type: 'ref' | 'reactive', time: number) {
this.metrics[`${type}Mutation`]++
this.mutationTimes.push(time)
// 保持最近1000次记录
if (this.mutationTimes.length > 1000) {
this.mutationTimes.shift()
}
}
getMetrics() {
return {
...toRaw(this.metrics),
averageAccessTime: this.accessTimes.length > 0
? this.accessTimes.reduce((a, b) => a + b, 0) / this.accessTimes.length
: 0,
averageMutationTime: this.mutationTimes.length > 0
? this.mutationTimes.reduce((a, b) => a + b, 0) / this.mutationTimes.length
: 0
}
}
reset() {
Object.keys(this.metrics).forEach(key => {
this.metrics[key] = 0
})
this.accessTimes = []
this.mutationTimes = []
}
}
// 使用性能监控器
const monitor = ReactivePerformanceMonitor.getInstance()
// 创建被监控的响应式数据
const monitoredRef = monitor.wrapRef(0)
const monitoredReactive = monitor.wrapReactive({
count: 0,
items: []
})
// 模拟操作
for (let i = 0; i < 1000; i++) {
monitoredRef.value++
monitoredReactive.count++
if (i % 10 === 0) {
monitoredReactive.items.push(i)
}
}
// 查看性能指标
console.log('Performance Metrics:', monitor.getMetrics())3.6.10 最佳实践与性能优化
1. 选择合适的工具API
typescript
// ✅ 正确使用toRefs进行解构
function useUserData() {
const state = reactive({
user: null,
loading: false,
error: null
})
// 返回时使用toRefs保持响应性
return {
...toRefs(state),
async fetchUser(id: string) {
state.loading = true
try {
state.user = await api.getUser(id)
} catch (err) {
state.error = err
} finally {
state.loading = false
}
}
}
}
// ❌ 错误:直接解构会失去响应性
function useUserDataWrong() {
const state = reactive({
user: null,
loading: false,
error: null
})
// 直接解构失去响应性
return {
...state, // ❌ 失去响应性
async fetchUser(id: string) {
// ...
}
}
}2. 合理使用customRef
typescript
// ✅ 适合使用customRef的场景
function useDebounceRef<T>(value: T, delay: number) {
// 需要自定义依赖追踪和触发逻辑
return customRef<T>((track, trigger) => {
let timeout: number
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
// ❌ 不适合使用customRef的场景
function useSimpleRef<T>(initialValue: T) {
// 简单的ref不需要customRef
return customRef<T>((track, trigger) => {
let value = initialValue
return {
get() {
track()
return value
},
set(newValue) {
value = newValue
trigger()
}
}
})
// 应该直接使用ref
// return ref(initialValue)
}3. 避免不必要的类型判断
typescript
// ✅ 在确定类型的情况下直接使用
function processKnownRef<T>(refValue: Ref<T>) {
// 已知是ref,直接访问.value
return refValue.value.toString()
}
// ✅ 在不确定类型时使用unref
function processUnknownValue<T>(value: MaybeRef<T>) {
// 使用unref统一处理
return unref(value).toString()
}
// ❌ 在已知类型时进行不必要的判断
function processKnownRefWrong<T>(refValue: Ref<T>) {
// 不必要的类型判断
if (isRef(refValue)) {
return refValue.value.toString()
}
return refValue.toString() // 这行永远不会执行
}4. 性能优化技巧
typescript
// ✅ 使用shallowRef + triggerRef优化大对象
function useLargeDataSet() {
const data = shallowRef({
items: [],
metadata: {},
cache: new Map()
})
const batchUpdate = (updates: Array<() => void>) => {
// 批量执行更新
updates.forEach(update => update())
// 统一触发更新
triggerRef(data)
}
return {
data: readonly(data),
batchUpdate,
addItem(item: any) {
data.value.items.push(item)
triggerRef(data)
},
updateMetadata(key: string, value: any) {
data.value.metadata[key] = value
triggerRef(data)
}
}
}
// ✅ 使用computed缓存昂贵计算
function useExpensiveComputation(source: Ref<any[]>) {
const expensiveResult = computed(() => {
// 昂贵的计算只在source变化时执行
return source.value
.filter(item => item.active)
.map(item => complexTransform(item))
.sort((a, b) => a.priority - b.priority)
})
return expensiveResult
}
// ❌ 避免在每次访问时重新计算
function useExpensiveComputationWrong(source: Ref<any[]>) {
return {
get result() {
// 每次访问都重新计算
return source.value
.filter(item => item.active)
.map(item => complexTransform(item))
.sort((a, b) => a.priority - b.priority)
}
}
}总结
Vue.js 3的响应式工具API为开发者提供了强大而灵活的数据处理能力:
核心设计原则
- 类型安全:通过TypeScript重载和泛型提供精确的类型推导
- 性能优化:支持浅层响应式、手动触发等性能优化策略
- 灵活性:customRef等API允许完全自定义响应式行为
- 易用性:unref、toValue等工具简化了常见操作
- 组合性:各种工具API可以组合使用,构建复杂的响应式逻辑
技术亮点
- 智能类型推导:toRefs、toRef等函数的复杂类型推导系统
- 自定义响应式:customRef提供的完全可控的响应式实现
- 性能优化机制:triggerRef、proxyRefs等性能优化工具
- 开发体验:类型判断、值提取等开发辅助工具
- 生态集成:与第三方库集成的便利性
实践价值
- 组合式API增强:在组合函数中灵活处理响应式数据
- 性能优化:在大型应用中优化响应式数据的性能
- 第三方集成:与现有库和框架的无缝集成
- 开发效率:减少样板代码,提高开发效率
- 类型安全:在TypeScript项目中提供完整的类型支持
这些工具API不仅展现了Vue 3响应式系统的强大能力,也为开发者在实际项目中灵活运用响应式编程提供了坚实的基础。理解和掌握这些工具的使用,对于构建高性能、可维护的Vue 3应用具有重要意义。
