AI 生成的摘要
这篇文章探讨了 Vue 3 中的 `ref` 和 `shallowRef` 的工作原理及其用法。`ref` 是一种允许单个数据变得响应式的 API,通过 `.value` 属性实现对数据的追踪和修改监听,适用于简单数据类型和复杂对象。当对象被赋予 `ref`,借助 `reactive()` 实现深层次响应式,使得对对象属性的更改同样被追踪。若想避免深层绑定,可以使用 `shallowRef`,它限制响应式仅作用于 `.value`,对内部属性的变更不会触发响应。文章还介绍了 `triggerRef` 用于强制更新浅层引用的依赖,`toRef` 及 `toRefs` 用于将响应式数据对象的属性转变为可独立追踪的 ref,从而实现双向绑定。这些机制不仅增进了性能优化,也增强了与外部状态管理系统的整合能力,对开发者在构建复杂的 Vue 应用时提供了强大的工具支持。

ref

接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value。 ref 对象是可更改的,也就是说你可以为 .value 赋予新的值。它也是响应式的,即所有对 .value 的操作都将被追踪,并且写操作会触发与之相关的副作用。 如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。这也意味着如果对象中包含了嵌套的 ref,它们将被深层地解包。 若要避免这种深层次的转换,请使用 shallowRef() 来替代。

ts
function ref<T>(value: T): Ref<UnwrapRef<T>>

interface Ref<T> {
  value: T
}

先来一个示例

ts
import { createApp, h, ref } from 'vueImpl'

const app = createApp({
  setup() {
    const count = ref(0)

    const countObj = ref({
      count: 0,
    })

    return () =>
      h('div', {}, [
        h('p', {}, [`count: ${count.value}`]),
        h('button', { onClick: () => count.value++ }, ['Increment1']),
        h('p', {}, [`countObj: ${countObj.value.count}`]),
        h('button', { onClick: () => countObj.value.count++ }, ['Increment2']),
        h('p', {}, [`countObj: ${countObj.value.count}`]),
        h('button', { onClick: () => countObj.value = { count: 100 } }, ['Update']),
      ])
  },
})

app.mount('#app')
  1. count给ref传入了原始类型,ref使其具有响应式(.value),走的是 ref 单独实现的响应式。
  2. countObj 给ref传入了对象,ref使其具有响应式(.value),修改countObj.value.count 时,走的是 reactive 的响应式(如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象)。
  3. 直接修改countObj.value,赋值为另一个对象,这里走的是 ref 的响应式,同时再使新赋值的对象具备响应式。
ts
export function ref(value?: unknown) {
  return createRef(value)
}

type RefBase<T> = {
  dep?: Dep
  value: T
}

export const toReactive = <T extends unknown>(value: T): T =>
  isObject(value) ? reactive(value) : value

function createRef(rawValue: unknown) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue)
}

class RefImpl<T> {
  private _value: T
  public dep?: Dep = undefined
  public readonly __v_isRef = true

  constructor(value: T) {
    // value toReactive => 如果是对象,则转化为reactive
    this._value = toReactive(value)
  }

  get value() {
    // 收集.value依赖
    trackRefValue(this)
    return this._value
  }

  set value(newVal) {
    // 重新赋值,并触发 trigger
    this._value = toReactive(newVal)
    triggerRefValue(this)
  }
}

export function trackRefValue(ref: RefBase<any>) {
  trackEffects(ref.dep || (ref.dep = createDep()))
}

export function triggerRefValue(ref: RefBase<any>) {
  if (ref.dep) triggerEffects(ref.dep)
}

// 收集依赖,ref执行时,activeEffect为当前组件的ReactiveEffect
export function trackEffects(dep: Dep) {
  if (activeEffect) {
    dep.add(activeEffect)
  }
}

// 修改时,触发更新
export function triggerEffects(dep: Dep | ReactiveEffect[]) {
  const effects = Array.isArray(dep) ? dep : [...dep]
  for (const effect of effects) {
    triggerEffect(effect)
  }
}

// 触发更新 
export function triggerEffect(effect: ReactiveEffect) {
  if (effect.scheduler) {
    effect.scheduler()
  } else {
    effect.run()
  }
}

shallowRef

和 ref() 不同,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。 shallowRef() 常常用于对大型数据结构的性能优化或是与外部的状态管理系统集成。

ts
const state = shallowRef({ count: 1 })

// 不会触发更改
state.value.count = 2

// 会触发更改
state.value = { count: 2 }
ts
function createRef(rawValue: unknown, shallow = false) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

class RefImpl<T> {
  private _value: T
  public dep?: Dep = undefined
  public readonly __v_isRef = true

  constructor(value: T, public readonly __v_isShallow: boolean,) {
    this._value = __v_isShallow ? value : toReactive(value)
  }

  get value() {
    trackRefValue(this)
    return this._value
  }

  set value(newVal) {
    this._value = this.__v_isShallow ? newVal : toReactive(newVal)
    triggerRefValue(this)
  }
}

declare const ShallowRefMarker: unique symbol
export type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true }

export function shallowRef<T extends object>(
  value: T,
): T extends Ref ? T : ShallowRef<T>
export function shallowRef<T>(value: T): ShallowRef<T>
export function shallowRef<T = any>(): ShallowRef<T | undefined>
export function shallowRef(value?: unknown) {
  return createRef(value, true)
}

只需要在toReactive时进行判断,如果是 ShallowRef 就不对 Object 进行代理。

triggerRef

强制触发依赖于一个浅层 ref 的副作用,这通常在对浅引用的内部值进行深度变更后使用。

ts
function triggerRef(ref: ShallowRef): void
ts
const shallow = shallowRef({
  greet: 'Hello, world'
})

// 触发该副作用第一次应该会打印 "Hello, world"
watchEffect(() => {
  console.log(shallow.value.greet)
})

// 这次变更不应触发副作用,因为这个 ref 是浅层的
shallow.value.greet = 'Hello, universe'

// 打印 "Hello, universe"
triggerRef(shallow)
ts
export function triggerRef(ref: Ref<any>) {
  triggerRefValue(ref)
}

这里直接 使用triggerRefValue,相当于 ref.value 触发,触发后数据肯定就更新了。

toRef

可以基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。

ts
export type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>
export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K,
): ToRef<T[K]>
export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K,
  defaultValue: T[K],
): ToRef<Exclude<T[K], undefined>>
export function toRef(
  source: Record<string, any>,
  key: string,
  defaultValue?: unknown,
): Ref {
  return propertyToRef(source, key!, defaultValue)
}

function propertyToRef(
  source: Record<string, any>,
  key: string,
  defaultValue?: unknown,
) {
  const val = source[key]
  return isRef(val)
    ? val
    : (new ObjectRefImpl(source, key, defaultValue) as any)
}

class ObjectRefImpl<T extends object, K extends keyof T> {
  public readonly __v_isRef = true

  constructor(
    private readonly _object: T,
    private readonly _key: K,
    private readonly _defaultValue?: T[K],
  ) {}

  get value() {
    const val = this._object[this._key]
    return val === undefined ? (this._defaultValue as T[K]) : val
  }

  set value(newVal) {
    this._object[this._key] = newVal
  }

  get dep(): Dep | undefined {
    return getDepFromReactive(this._object, this._key)
  }
}

首先判断目标值是否是 ref,如果是就不用处理,直接返回,如果不是,直接代理 value 的 get 和 set (如果source是 reactive 那自会响应式)

toRefs

将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。

ts
function toRefs<T extends object>(
  object: T
): {
  [K in keyof T]: ToRef<T[K]>
}

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

export function toRefs<T extends object>(object: T): ToRefs<T> {
  const ret: any = Array.isArray(object) ? new Array(object.length) : {}
  for (const key in object) {
    ret[key] = propertyToRef(object, key)
  }
  return ret
}