Skip to content

第3.6节:响应式工具:toRefs、customRef 等 API 的实现与应用场景

Vue.js 3的响应式系统不仅提供了基础的refreactiveAPI,还提供了一系列强大的工具函数,用于在不同场景下灵活地处理响应式数据。这些工具API包括toRefscustomReftriggerRef等,它们在组合式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) // 现在会触发watchEffect

2. 性能优化的批量更新

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')) // getter

2. 条件计算

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' // 触发watchEffect

2. 响应式数据的时间旅行调试

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为开发者提供了强大而灵活的数据处理能力:

核心设计原则

  1. 类型安全:通过TypeScript重载和泛型提供精确的类型推导
  2. 性能优化:支持浅层响应式、手动触发等性能优化策略
  3. 灵活性:customRef等API允许完全自定义响应式行为
  4. 易用性:unref、toValue等工具简化了常见操作
  5. 组合性:各种工具API可以组合使用,构建复杂的响应式逻辑

技术亮点

  1. 智能类型推导:toRefs、toRef等函数的复杂类型推导系统
  2. 自定义响应式:customRef提供的完全可控的响应式实现
  3. 性能优化机制:triggerRef、proxyRefs等性能优化工具
  4. 开发体验:类型判断、值提取等开发辅助工具
  5. 生态集成:与第三方库集成的便利性

实践价值

  1. 组合式API增强:在组合函数中灵活处理响应式数据
  2. 性能优化:在大型应用中优化响应式数据的性能
  3. 第三方集成:与现有库和框架的无缝集成
  4. 开发效率:减少样板代码,提高开发效率
  5. 类型安全:在TypeScript项目中提供完整的类型支持

这些工具API不仅展现了Vue 3响应式系统的强大能力,也为开发者在实际项目中灵活运用响应式编程提供了坚实的基础。理解和掌握这些工具的使用,对于构建高性能、可维护的Vue 3应用具有重要意义。


微信公众号二维码