vue3源码之ref

ref

ref 流程图

Ref 可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构。

Ref 会使它的值具有深层响应性。这意味着即使改变嵌套对象或数组时,变化也会被检测到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar'],
})

watchEffect(() => {
console.log(obj.value.nested.count)
})

watchEffect(() => {
console.log(obj.value.arr.length)
})

obj.value.nested.count = 1000
obj.value.arr.push('baz')
// 0 obj.nested.count 初始值
// 2 obj.arr.length 初始值
// 1000 obj.nested.count 变化后
// 3 obj.arr.length 变化后

ref 入口函数

1
2
3
4
// packages/reactivity/src/ref.ts
export function ref(value?: unknown) {
return createRef(value, false)
}

传入一个参数 value,通过 createRef 函数创建返回一个 ref 对象,该对象只有一个指向 value 的属性 .value。

createRef() 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// packages/reactivity/src/constants.ts
export enum ReactiveFlags {
IS_REF = '__v_isRef',
}

// packages/reactivity/src/ref.ts
export function isRef(r: any): r is Ref {
return r ? r[ReactiveFlags.IS_REF] === true : false
}

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

通过 createRef() 函数创建一个 ref 对象

  • value 为 ref 对象的初始值
  • shallow 为是否为浅响应式,默认值为 false。

isRef() 函数用于检查某个值是否为 ref。

  • 如果值有 ReactiveFlags.IS_REF(__v_isRef) 属性,则该值已经是一个 ref 对象,直接返回 rawValue。
  • 否则使用 RefImpl 类创建一个 ref 对象。

RefImpl 类

RefImpl 类源码

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// packages/reactivity/src/ref.ts
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()
}
}
}
}

this.dep

1
dep: Dep = new Dep()

每个 RefImpl 实例都有一个独立的 Dep 实例,它维护了一个订阅者集合。当你在模板中使用了一个 ref,然后改变了这个 ref 的值时,Vue 会自动检测到这个变化,并且相应地更新 DOM。这是通过一个基于依赖追踪的响应式系统实现的。当一个组件首次渲染时,Vue 会追踪在渲染过程中使用的每一个 ref。然后,当一个 ref 被修改时,它会触发追踪它的组件的一次重新渲染。

constructor()

1
2
3
4
5
6
7
8
// packages/reactivity/src/reactive.ts
export function toRaw<T>(observed: T): T {
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
}

// packages/reactivity/src/ref.ts
this._rawValue = isShallow ? value : toRaw(value)

根据是否为浅响应式,设置 value 的原始值 _rawValue。

  • 如果是浅响应式,直接返回value。
  • 如果不是浅响应式,通过 toRaw 函数获取 value 的原始值,value 可能是一个响应式对象。

toRaw() 可以返回由 reactive()、readonly()、shallowReactive() 或者 shallowReadonly() 创建的代理对应的原始对象。

这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。

场景一

1
2
3
4
const ageProxy = reactive({
age: 18,
})
const ageRef = shallowRef(ageProxy)

通过 shallowRef 函数创建一个浅响应式的 ref 对象。此时 isShallow 为 true,value 为 Proxy 代理对象,此时 _rawValue 为 Proxy 代理对象。

场景二

1
2
3
4
const ageProxy = reactive({
age: 18,
})
const ageRef = ref(ageProxy)

通过 ref 函数创建一个响应式 ref 对象。此时 isShallow 为 false, value 为 Proxy 代理对象。此时 _rawValue 为 toRaw(value) 的原始值,即 { age: 18 }。


1
2
3
4
5
6
// packages/reactivity/src/reactive.ts
export const toReactive = <T extends unknown>(value: T): T =>
isObject(value) ? reactive(value) : value

// packages/reactivity/src/ref.ts
this._value = isShallow ? value : toReactive(value)

根据是否为浅响应式,设置 value 的处理值 _value。

  • 如果是浅响应式,直接返回value。
  • 如果不是浅响应式,通过 toReactive 函数转换为响应式值。

由此可知,当对象调用 ref(obj) 函数时,底层使用的是 reactive(obj)。


1
this[ReactiveFlags.IS_SHALLOW] = isShallow

设置 RefImpl 类的属性值 ReactiveFlags.IS_SHALLOW 为 isShallow,用于判断是否为浅响应式 ref 对象。

get()

1
2
3
4
5
6
7
8
9
10
11
12
get value() {
if (__DEV__) {
this.dep.track({
target: this,
type: TrackOpTypes.GET,
key: 'value',
})
} else {
this.dep.track()
}
return this._value
}

在标准的 JavaScript 中,检测普通变量的访问或修改是行不通的。然而,我们可以通过 getter 和 setter 方法来拦截对象属性的 get 和 set 操作。

value 属性给予了 Vue 一个机会来检测 ref 何时被访问或修改。在其内部,Vue 在它的 getter 中执行追踪,在它的 setter 中执行触发。

如果是 DEV 环境,依赖收集时添加 debugInfo 信息,用于调试时查看依赖关系。

场景示例

1
2
3
4
5
6
7
8
const ageProxy = reactive({
age: 18,
})
const ageRef = ref(ageProxy)

const ageValue = ageRef.value

// Proxy(Object) { age: 18 }

set()

1
2
3
4
5
const useDirectValue =
this[ReactiveFlags.IS_SHALLOW] ||
isShallow(newValue) ||
isReadonly(newValue)
newValue = useDirectValue ? newValue : toRaw(newValue)

设置 ref 响应式对象的新值 newValue。

  • 如果是浅响应式 ref,或者 newValue 是浅响应式值,或者 newValue 是只读值,则使用直接值, newValue = newValue。
  • 否则通过 toRaw 函数转换为原始值, newValue = toRaw(newValue)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// packages/reactivity/src/general.ts
export const hasChanged = (value: any, oldValue: any): boolean =>
!Object.is(value, oldValue)

// packages/reactivity/src/ref.ts
const oldValue = this._rawValue
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()
}
}

通过对比 newValue 和 oldValue 是否为同一对象,来判断值是否改变。

如果值改变

  1. 更新 this.\_rawValue 的值为 newValue
  2. 根据是否使用直接值更新 this.\_value 的值。如果为 useDirectValue 为true,this._value 为 newValue 。否则通过 toReactive 函数转换为响应式值,此时 this._value 为 toReactive(newValue) 。
  3. 触发依赖收集的组件重新渲染。

场景示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const proxyRef = ref({})
const nameRef = ref({})
const ageProxy = readonly({
age: 19,
})

// 执行 setter 操作
// useDirectValue 为 false
// newValue = {age: 18}
// _value = toReactive({age: 18})
proxyRef.value = {
age: 18,
}
// 执行 setter 操作
// isReadonly(newValue) 为 true,useDirectValue 为 true,
// newValue = ageProxy
// _value = newValue = ageProxy
nameRef.value = ageProxy


vue3源码之ref
https://www.my-web.cn/vue/vue3源码之ref/
发布于
2026年8月25日
许可协议