Ref
# 目录
# 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)
}
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)
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 的劫持,仍然是一种很高效的响应式方法。
参考资料:
# 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)
}
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)
}
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
}
}
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
}
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
}
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)
}
}
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)
}
2
3
triggerRef 就是对 ref 对象上收集的依赖进行手动触发和执行。
# 文章小结
本文是对 vue3 响应式原理部分 Ref Api 进行源码剖析。更深的理解需要参照 reactive 和 effect 部分。