Fancy Front End Fancy Front End
  • 开始上手
  • 基础
  • 调度器(Scheduler)
  • 更新器(Updater)
  • 渲染器(Render)
  • 更新周期
  • hooks 原理
  • 总结
  • 📙 React源码漂流记
  • 开始上手
  • 基础
  • reactivity
  • runtime-core
  • runtime-dom
  • Awesome Web
  • Awesome NodeJS
话题
  • 导航
  • Q&A
  • 幻灯片
  • 关于
  • 分类
  • 标签
  • 归档
博客 (opens new window)
GitHub (opens new window)

Jonsam NG

让有意义的事变得有意思,让有意思的事变得有意义
  • 开始上手
  • 基础
  • 调度器(Scheduler)
  • 更新器(Updater)
  • 渲染器(Render)
  • 更新周期
  • hooks 原理
  • 总结
  • 📙 React源码漂流记
  • 开始上手
  • 基础
  • reactivity
  • runtime-core
  • runtime-dom
  • Awesome Web
  • Awesome NodeJS
话题
  • 导航
  • Q&A
  • 幻灯片
  • 关于
  • 分类
  • 标签
  • 归档
博客 (opens new window)
GitHub (opens new window)
  • 开始上手
  • plan 计划
  • 基础

  • reactivity

    • 开始上手
    • Ref
      • 目录
      • ref
      • shallowRef
      • isRef
      • toRef
      • toRefs
      • unref
      • customRef
      • triggerRef
      • 文章小结
    • Reactive
    • Handler
    • Effect
    • Computed
  • runtime-core

  • runtime-dom

  • vue3
  • reactivity
jonsam
2022-04-14
目录

Ref

# 目录

  • 目录
  • ref
  • shallowRef
  • isRef
  • toRef
  • toRefs
  • unref
  • customRef
  • triggerRef
  • 文章小结

# ref

标签: 重要

官网释义

Takes an inner value and returns a reactive and mutable ref object. The ref object has a single property .value that points to the inner value.

接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值的单个 property .value。

  • ref 接受一个 unknown 类型的值,内部调用了 createRef 函数。
  • createRef 内部对于本身已经是 ref 的传值返回其本身,返回了一个 RefImpl 对象实例。有此可见一个 ref 兑现个,实际上是一个 RefImpl 的对象实例。同时我们注意到,ref 传入 RefImpl 中 shadow 的值是 false。也就是 ref 创建的响应式是 deep reactivity 的。
export function ref(value?: unknown) {
  return createRef(value)
}
function createRef(rawValue: unknown, shallow = false) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}
1
2
3
4
5
6
7
8
9

接着我们来看下核心 class RefImpl:

class RefImpl<T> {
  private _value: T

  public readonly __v_isRef = true

  // 如果 shadow 或者是不是对象 则 _rawValue,是对象就调 reactive。
  // 这里给 _value 赋初始值
  constructor(private _rawValue: T, public readonly _shallow: boolean) {
    // 初始化设置值
    this._value = _shallow ? _rawValue : convert(_rawValue)
  }
  
  get value() {
    // 初始化依赖收集,这里的 target 是 row target
    track(toRaw(this), TrackOpTypes.GET, 'value')
    return this._value
  }

  set value(newVal) {
    // 检查值是否有变化,只有有变化时才出发 setter
    // this._rawValue 是上一次 set 时传的 rowValue
    if (hasChanged(toRaw(newVal), this._rawValue)) {
      this._rawValue = newVal
      this._value = this._shallow ? newVal : convert(newVal)
      // 触发(消费)依赖集合,注意这里传入了 newVal
      trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
    }
  }
}
// 如果是传入对象就简介调用 reactive
const convert = <T extends unknown>(val: T): T =>
  isObject(val) ? reactive(val) : val
// compare whether a value has changed, accounting for NaN.
// 这里是考虑到 NaN 的情况,因为 NaN === NaN 是 false,但是 null === null 是 true。
export const hasChanged = (value: any, oldValue: any): boolean =>
  value !== oldValue && (value === value || oldValue === oldValue)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  • 在这段代码中,实现了 ref 的响应式,我们知道 vue 的响应式都是基于对响应式 target 的值的 get 和 set 的拦截来实现的,简单的说就是 在 get 时对 target 进行依赖收集,在 set 时触发依赖回调集合来实现响应式的。在 vue2 中的 Object.defineProperty (),以及 vue3 中的 Proxy 都是这样的原理。
  • _value 维护了 ref 中响应式的值,__v_isRef 是 ref 对象的身份标识。后文中 isRef 正在通过这个表示来辨别 ref 对象。
  • getter 和 setter 属于 es6 的语法。在 value 的 getter 中调用了 track 函数,这个函数对 target 也就是 toRaw (this) 进行了依赖收集,也就是任何应用到当前的 ref 值的地方都会被记录。在 value 的 setter 中先是比较新值和旧值是否有变化,只有变化的值才会引起响应式更新,trigger 函数正在对当前 target 所收集的依赖进行执行。track 和 trigger 实际上是使用了订阅发布的设计模式,我们将会在 effect 部分详细分析。
  • 响应式我们已经有 reactive 了,为什么还要有 ref? 从 convert 的代码中可以看出,ref 的传值如果是 object 时实际上是间接使用了 reactive 来保持 deep reactivity 的,然后才被包装成了 ref 对象。Proxy Api 是只用于 object 中的,这一点从 MDN 的解释 'The Proxy object enables you to create a proxy for another object, which can intercept and redefine fundamental operations for that object.' 可以看出。因此针对普通值类型的响应式,我们还是需要由 ref 来实现的。而且对于普通值类型的响应式而言,并不存在 object property 的劫持,仍然是一种很高效的响应式方法。

参考资料:

  • MDN: Object.defineProperty (opens new window)
  • MDN: Proxy (opens new window)

# shallowRef

官网释义

Creates a ref that tracks its own .value mutation but doesn't make its value reactive.

创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的。

export function shallowRef(value?: unknown) {
  return createRef(value, true)
}
1
2
3

可以看到,shallowRef 同样是调用 shallowRef,只不过 _shallow 传入了 true。与 ref 不同点就在于:在针对 object 的 setter 时,并不在是使用 reactive 了,而是直接使用_rawValue。有此可见,如果传入了 Object,则只是 object 顶层响应式的,也就是 ref.value 是响应式的,这是的 isReactive (ref.value) 应该是 false 。

# isRef

官网释义

Checks if a value is a ref object.

检查值是否为一个 ref 对象。

export function isRef(r: any): r is Ref {
  return Boolean(r && r.__v_isRef === true)
}
1
2
3

根据 __v_isRef 标志判断是否是 ref 对象。

# toRef

官网释义

Can be used to create a ref for a property on a source reactive object. The ref can then be passed around, retaining the reactive connection to its source property.

可以用来为源响应式对象上的某个 property 新创建一个 ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。

export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K
): ToRef<T[K]> {
  return isRef(object[key])
    ? object[key]
    : (new ObjectRefImpl(object, key) 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) {}

  get value() {
    return this._object[this._key]
  }

  set value(newVal) {
    this._object[this._key] = newVal
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

内部仍然是 ObjectRefImpl 实现。

# toRefs

官网释义

Converts a reactive object to a plain object where each property of the resulting object is a ref pointing to the corresponding property of the original object.

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

export function toRefs<T extends object>(object: T): ToRefs<T> {
  const ret: any = isArray(object) ? new Array(object.length) : {}
  for (const key in object) {
    ret[key] = toRef(object, key)
  }
  return ret
}
1
2
3
4
5
6
7
  • toRefs 支持 array 和 object。

# unref

官网释义

Returns the inner value if the argument is a ref, otherwise return the argument itself. This is a sugar function for val = isRef(val) ? val.value : val.

如果参数是一个 ref,则返回内部值,否则返回参数本身。这是 val = isRef (val) ? val.value : val 的语法糖函数。

export function unref<T>(ref: T | Ref<T>): T {
  return isRef(ref) ? (ref.value as any) : ref
}
1
2
3

# customRef

官网释义

Creates a customized ref with explicit control over its dependency tracking and updates triggering. It expects a factory function, which receives track and trigger functions as arguments and should return an object with get and set.

创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 track 和 trigger 函数作为参数,并且应该返回一个带有 get 和 set 的对象。

export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
  return new CustomRefImpl(factory) as any
}
class CustomRefImpl<T> {
  private readonly _get: ReturnType<CustomRefFactory<T>>['get']
  private readonly _set: ReturnType<CustomRefFactory<T>>['set']

  public readonly __v_isRef = true

  constructor(factory: CustomRefFactory<T>) {
    // 将 track 和 trigger 传入默认的参数包装成函数传递给工厂函数。
    // 执行工厂函数获取 getter 和 setter。
    const { get, set } = factory(
      () => track(this, TrackOpTypes.GET, 'value'),
      () => trigger(this, TriggerOpTypes.SET, 'value')
    )
    this._get = get
    this._set = set
  }

  get value() {
    return this._get()
  }

  set value(newVal) {
    this._set(newVal)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

从 CustomRefImpl 可以看出:

  • ref 响应式的核心就是对 value 的 getter 和 setter 的拦截,在 getter 最重要的两步就是 track 和赋值,在 setter 中最重要的就是 trigger 和设置值。track 和 trigger 就是 vue 响应式的核心。
  • customRef 可以通过对 getter 和 setter 的自定义来实现响应式的不同的效果。可以对 ref 的响应式做定制。

# triggerRef

官网释义

Execute any effects tied to a shallowRef manually.

手动执行与 shallowRef 关联的任何副作用。

export function triggerRef(ref: Ref) {
  trigger(toRaw(ref), TriggerOpTypes.SET, 'value', __DEV__ ? ref.value : void 0)
}
1
2
3

triggerRef 就是对 ref 对象上收集的依赖进行手动触发和执行。

# 文章小结

本文是对 vue3 响应式原理部分 Ref Api 进行源码剖析。更深的理解需要参照 reactive 和 effect 部分。

编辑 (opens new window)
上次更新: 2022/04/15, 00:23:56
开始上手
Reactive

← 开始上手 Reactive→

最近更新
01
渲染原理之组件结构与 JSX 编译
09-07
02
计划跟踪
09-06
03
开始上手
09-06
更多文章>
Theme by Vdoing | Copyright © 2022-2022 Fancy Front End | Made by Jonsam by ❤
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式