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')
|
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
| export enum ReactiveFlags { IS_REF = '__v_isRef', }
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
| 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
每个 RefImpl 实例都有一个独立的 Dep 实例,它维护了一个订阅者集合。当你在模板中使用了一个 ref,然后改变了这个 ref 的值时,Vue 会自动检测到这个变化,并且相应地更新 DOM。这是通过一个基于依赖追踪的响应式系统实现的。当一个组件首次渲染时,Vue 会追踪在渲染过程中使用的每一个 ref。然后,当一个 ref 被修改时,它会触发追踪它的组件的一次重新渲染。
constructor()
1 2 3 4 5 6 7 8
| export function toRaw<T>(observed: T): T { const raw = observed && (observed as Target)[ReactiveFlags.RAW] return raw ? toRaw(raw) : observed }
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
| export const toReactive = <T extends unknown>(value: T): T => isObject(value) ? reactive(value) : value
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
|
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
| export const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue)
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 是否为同一对象,来判断值是否改变。
如果值改变
- 更新
this.\_rawValue 的值为 newValue
- 根据是否使用直接值更新
this.\_value 的值。如果为 useDirectValue 为true,this._value 为 newValue 。否则通过 toReactive 函数转换为响应式值,此时 this._value 为 toReactive(newValue) 。
- 触发依赖收集的组件重新渲染。
场景示例
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, })
proxyRef.value = { age: 18, }
nameRef.value = ageProxy
|
