Vue 3 Ref 源码解析

247 阅读9分钟

Vue 3 的响应式系统由两大核心部分组成:reactive 和 ref。如果说 reactive 是处理对象的响应式,那么 ref 就是处理基本数据类型的响应式。本文将深入分析 Vue 3 中 ref.ts 文件的实现原理,通过丰富的示例帮助你全面掌握 Vue 3 Ref 系统。

目录

1. 概述

ref.ts 是 Vue 3 响应式系统的核心模块之一,专门用于处理基本数据类型的响应式。它通过创建一个包含 value 属性的对象来包装基本数据类型,使它们具备响应式能力。

Ref 系统提供了多种 API 来满足不同场景的需求:

  • ref: 创建深度响应式 ref
  • shallowRef: 创建浅层响应式 ref
  • isRef: 检查值是否为 ref 对象
  • unref: 解包 ref 对象
  • toRef: 将对象属性转换为 ref
  • toRefs: 将响应式对象转换为 ref 对象集合
  • customRef: 创建自定义 ref
  • triggerRef: 手动触发浅层 ref 的更新

2. 核心 API 详解

2.1 ref

ref 是最基本的 Ref API,用于创建一个响应式的 ref 对象。

export function ref<T>(
  value: T,
): [T] extends [Ref] ? IfAny<T, Ref<T>, T> : Ref<UnwrapRef<T>, UnwrapRef<T> | T>
export function ref<T = any>(): Ref<T | undefined>

特点:

  • 创建一个包含 value 属性的响应式对象
  • 对值的访问和修改都需要通过 value 属性
  • 会递归处理对象和数组的内部属性

使用示例:

import { ref, watchEffect } from 'vue'

// 基本类型
const count = ref(0)
console.log(count.value) // 0

// 修改值
count.value++
console.log(count.value) // 1

// 对象类型
const state = ref({
  name: 'Vue',
  version: 3
})

// 响应式访问
watchEffect(() => {
  console.log(state.value.name) // Vue
})

// 修改对象属性
state.value.name = 'Vue 3'
// 会触发 watchEffect 重新执行

2.2 shallowRef

shallowRef 创建一个浅层响应式的 ref 对象。

export function shallowRef<T>(
  value: T,
): Ref extends T
  ? T extends Ref
    ? IfAny<T, ShallowRef<T>, T>
    : ShallowRef<T>
  : ShallowRef<T>

特点:

  • 只有对 .value 的修改是响应式的
  • 不会对值内部属性的修改做出响应
  • 适用于大型数据结构,避免不必要的性能开销

使用示例:

import { shallowRef, watchEffect, triggerRef } from 'vue'

const state = shallowRef({
  count: 1,
  list: [{ id: 1, name: 'Item 1' }]
})

// 响应式监听
watchEffect(() => {
  console.log(state.value.count)
})

// 修改 .value 是响应式的
state.value = { count: 2, list: [] } 
// 会触发 watchEffect 重新执行

// 修改内部属性不是响应式的
state.value.count = 3
// 不会触发 watchEffect 重新执行

// 需要手动触发更新
triggerRef(state)
// 现在会触发 watchEffect 重新执行

2.3 isRef

检查一个值是否为 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
}

使用示例:

import { ref, isRef } from 'vue'

const count = ref(0)
const notRef = 1

console.log(isRef(count))    // true
console.log(isRef(notRef))   // false

2.4 unref

如果参数是 ref,则返回其内部值,否则直接返回参数本身。

export function unref<T>(ref: MaybeRef<T> | ComputedRef<T>): T {
  return isRef(ref) ? ref.value : ref
}

使用示例:

import { ref, unref } from 'vue'

const count = ref(1)
const normalValue = 2

console.log(unref(count))       // 1
console.log(unref(normalValue)) // 2

// 等价于
console.log(isRef(count) ? count.value : count) // 1

2.5 toRef

将值标准化为 ref,或将响应式对象的属性转换为 ref。

export function toRef<T>(
  value: T,
): T extends () => infer R
  ? Readonly<Ref<R>>
  : T extends Ref
    ? T
    : Ref<UnwrapRef<T>>
export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K,
): ToRef<T[K]>

使用示例:

import { reactive, toRef } from 'vue'

const state = reactive({
  foo: 1,
  bar: 2
})

// 将属性转换为 ref
const fooRef = toRef(state, 'foo')

// 修改 ref 会影响原始对象
fooRef.value++
console.log(state.foo) // 2

// 修改原始对象也会影响 ref
state.foo++
console.log(fooRef.value) // 3

2.6 toRefs

将响应式对象转换为普通对象,其中每个属性都是指向原始对象相应属性的 ref。

export function toRefs<T extends object>(object: T): ToRefs<T>

使用示例:

import { reactive, toRefs } from 'vue'

const state = reactive({
  foo: 1,
  bar: 2
})

// 转换为 refs
const { foo, bar } = toRefs(state)

// 现在可以解构并保持响应式
console.log(foo.value) // 1
console.log(bar.value) // 2

// 修改 ref 会影响原始对象
foo.value++
console.log(state.foo) // 2

2.7 toValue

标准化值/ref/getter 为值。

export function toValue<T>(source: MaybeRefOrGetter<T>): T {
  return isFunction(source) ? source() : unref(source)
}

使用示例:

import { ref, toValue } from 'vue'

const count = ref(1)
const getCount = () => 2
const normalValue = 3

console.log(toValue(count))      // 1
console.log(toValue(getCount))   // 2
console.log(toValue(normalValue)) // 3

2.8 customRef

创建一个自定义的 ref,可以显式控制依赖追踪和更新触发。

export function customRef<T>(factory: CustomRefFactory<T>): Ref<T>

使用示例:

import { customRef } from 'vue'

// 创建一个防抖 ref
function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track() // 通知 Vue 追踪依赖
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger() // 通知 Vue 触发更新
        }, delay)
      }
    }
  })
}

const debouncedValue = useDebouncedRef('hello')

2.9 triggerRef

手动触发浅层 ref 的依赖更新。

export function triggerRef(ref: Ref): void

使用示例:

import { shallowRef, watchEffect, triggerRef } from 'vue'

const shallow = shallowRef({
  greet: 'Hello, world'
})

watchEffect(() => {
  console.log(shallow.value.greet)
}) 
// 输出: Hello, world

// 修改内部属性不会触发更新
shallow.value.greet = 'Hello, universe'

// 手动触发更新
triggerRef(shallow)
// 输出: Hello, universe

2.10 proxyRefs

创建一个代理对象,该对象会自动解包属性中的 ref。

export function proxyRefs<T extends object>(
  objectWithRefs: T,
): ShallowUnwrapRef<T>

使用示例:

import { ref, proxyRefs } from 'vue'

const state = {
  foo: ref(1),
  bar: ref(2)
}

const stateProxy = proxyRefs(state)

// 可以直接访问 ref 的值
console.log(stateProxy.foo) // 1
console.log(stateProxy.bar) // 2

// 修改值也会更新原始 ref
stateProxy.foo = 3
console.log(state.foo.value) // 3

3. 工具类型解析

3.1 Ref 接口

export interface Ref<T = any, S = T> {
  get value(): T
  set value(_: S)
  [RefSymbol]: true
}

Ref 接口定义了 ref 对象的基本结构,包含 value 的 getter 和 setter,以及用于类型识别的 RefSymbol

3.2 MaybeRef 和 MaybeRefOrGetter

export type MaybeRef<T = any> =
  | T
  | Ref<T>
  | ShallowRef<T>
  | WritableComputedRef<T>

export type MaybeRefOrGetter<T = any> = MaybeRef<T> | ComputedRef<T> | (() => T)

这些类型用于表示一个值可能是普通值、ref、浅层 ref 或计算属性,增强了类型安全性。

3.3 UnwrapRef 和相关类型

export type UnwrapRef<T> =
  T extends ShallowRef<infer V, unknown>
    ? V
    : T extends Ref<infer V, unknown>
      ? UnwrapRefSimple<V>
      : UnwrapRefSimple<T>

这些类型用于解包 ref 类型,获取其内部值的类型,支持递归解包复杂类型。

4. 内部实现机制

4.1 RefImpl 类

Ref 的核心实现是 RefImpl 类:

class RefImpl<T = any> {
  _value: T
  private _rawValue: T
  dep: Dep = new Dep()
  public readonly [ReactiveFlags.IS_REF] = true
  public readonly [ReactiveFlags.IS_SHALLOW]: boolean = false

  constructor(value: T, isShallow: boolean) {
    this._rawValue = isShallow ? value : toRaw(value)
    this._value = isShallow ? value : toReactive(value)
    this[ReactiveFlags.IS_SHALLOW] = isShallow
  }

  get value() {
    if (__DEV__) {
      this.dep.track({
        target: this,
        type: TrackOpTypes.GET,
        key: 'value',
      })
    } else {
      this.dep.track()
    }
    return this._value
  }

  set value(newValue) {
    const oldValue = this._rawValue
    const useDirectValue =
      this[ReactiveFlags.IS_SHALLOW] ||
      isShallow(newValue) ||
      isReadonly(newValue)
    newValue = useDirectValue ? newValue : toRaw(newValue)
    if (hasChanged(newValue, oldValue)) {
      this._rawValue = newValue
      this._value = useDirectValue ? newValue : toReactive(newValue)
      if (__DEV__) {
        this.dep.trigger({
          target: this,
          type: TriggerOpTypes.SET,
          key: 'value',
          newValue,
          oldValue,
        })
      } else {
        this.dep.trigger()
      }
    }
  }
}

4.2 依赖追踪机制

Ref 通过 Dep 类实现依赖追踪:

5. TypeScript 高级特性应用

5.1 泛型与条件类型

export function ref<T>(
  value: T,
): [T] extends [Ref] ? IfAny<T, Ref<T>, T> : Ref<UnwrapRef<T>, UnwrapRef<T> | T>

使用条件类型根据传入值的类型决定返回类型。

5.2 映射类型

export type ToRefs<T = any> = {
  [K in keyof T]: ToRef<T[K]>
}

使用映射类型将对象的所有属性转换为 ref。

5.3 infer 关键字

T extends () => infer R
  ? Readonly<Ref<R>>
  : T extends Ref
    ? T
    : Ref<UnwrapRef<T>>

使用 infer 关键字从函数类型中推断返回值类型。

5.4 联合类型与交叉类型

export type MaybeRef<T = any> =
  | T
  | Ref<T>
  | ShallowRef<T>
  | WritableComputedRef<T>

使用联合类型表示多种可能的类型。

5.5 unique symbol

declare const RefSymbol: unique symbol

使用 unique symbol 创建全局唯一的标识符。

5.6 函数重载

export function toRef<T>(
  value: T,
): T extends () => infer R
  ? Readonly<Ref<R>>
  : T extends Ref
    ? T
    : Ref<UnwrapRef<T>>
export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K,
): ToRef<T[K]>

通过函数重载提供多种调用方式。

6. 实际应用示例

6.1 基础使用

import { ref, watchEffect } from 'vue'

// 创建响应式数据
const count = ref(0)
const message = ref('Hello Vue!')

// 响应式计算
const doubled = ref(0)
watchEffect(() => {
  doubled.value = count.value * 2
})

// 修改数据
count.value++
console.log(doubled.value) // 2

6.2 组合式函数中使用 Ref

import { ref, watch } from 'vue'

// 自定义组合式函数
export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = initialValue
  
  return { count, increment, decrement, reset }
}

// 使用组合式函数
const { count, increment, decrement, reset } = useCounter(10)
increment()
console.log(count.value) // 11

6.3 表单处理

import { ref, reactive } from 'vue'

export function useForm(initialData) {
  const formData = reactive(initialData)
  const errors = ref({})
  const isSubmitting = ref(false)
  
  const validate = () => {
    // 表单验证逻辑
    const newErrors = {}
    // ...验证逻辑
    errors.value = newErrors
    return Object.keys(newErrors).length === 0
  }
  
  const submit = async (submitFn) => {
    if (!validate()) return
    
    isSubmitting.value = true
    try {
      await submitFn(formData)
    } finally {
      isSubmitting.value = false
    }
  }
  
  return {
    formData,
    errors,
    isSubmitting,
    submit,
    validate
  }
}

// 使用表单处理
const { formData, errors, isSubmitting, submit } = useForm({
  name: '',
  email: ''
})

6.4 自定义 Ref

import { customRef } from 'vue'

// 创建一个支持本地存储的 ref
function useLocalStorage(key, defaultValue) {
  const storedValue = localStorage.getItem(key)
  const initial = storedValue ? JSON.parse(storedValue) : defaultValue
  
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return initial
      },
      set(value) {
        initial = value
        localStorage.setItem(key, JSON.stringify(value))
        trigger()
      }
    }
  })
}

// 使用本地存储 ref
const userName = useLocalStorage('userName', 'Anonymous')
console.log(userName.value) // Anonymous

userName.value = 'John'
// 值会自动保存到 localStorage

7. 性能优化策略

  1. 合理使用 shallowRef: 对于大型数据结构,使用 shallowRef 避免不必要的响应式开销
  2. 批量更新: 对于需要频繁修改的数据,考虑批量更新以减少触发次数
  3. 避免不必要的 toRefs: 只在需要解构并保持响应式时使用 toRefs
  4. 使用 triggerRef: 在修改 shallowRef 内部属性后手动触发更新

8. 常见问题与解决方案

8.1 直接解构失去响应式

// 错误做法
const state = reactive({ count: 0 })
const { count } = state // 失去响应式

// 正确做法
const state = reactive({ count: 0 })
const { count } = toRefs(state) // 保持响应式

8.2 修改 shallowRef 内部属性不更新

const state = shallowRef({ count: 0 })

// 错误:不会触发更新
state.value.count++

// 正确:手动触发更新
state.value.count++
triggerRef(state)

8.3 在模板中访问 ref 值

<template>
  <!-- 错误:在模板中不需要 .value -->
  <div>{{ count.value }}</div>
  
  <!-- 正确:模板中自动解包 -->
  <div>{{ count }}</div>
</template>

9. 总结

Vue 3 的 Ref 系统通过巧妙地结合 getter/setter、依赖追踪和 TypeScript 类型系统,实现了强大而灵活的响应式能力。它不仅提供了多种 API 满足不同场景需求,还通过各种优化策略保证了良好的性能。