1.ref
在 Vue 3 中,ref
的实现主要依赖于 Proxy
对象和 Dep
(依赖)机制来追踪依赖关系和触发更新。不过,对于基本数据类型,ref
并不会直接使用 Proxy
,因为 Proxy
需要一个对象作为目标,而基本数据类型本身不是对象。相反,ref
会创建一个包含 value
属性的对象来包装基本数据类型,从而使其能够被响应式系统所管理。
下面是一个简化的 ref
源码分析:
源码位置
ref
的定义位于 @vue/reactivity
包中的 src/ref.ts
文件中。
源码分析
// @vue/reactivity/src/ref.ts
import { isObject, hasOwn, isFunction } from '@vue/shared'
import { track, trigger } from './effect'
import { ReactiveFlags, Target, TargetWithMemo } from './reactive'
import { EffectScope, recordEffectScope } from './effectScope'
import { toRaw, toReactive } from './reactive'
export interface Ref<T> {
value: T
__v_isRef: true
}
export function ref<T>(value: T): Ref<UnwrapRef<T>> {
return createRef(value, false)
}
function createRef(rawValue: unknown, shallow: boolean): Ref<unknown> {
if (isRef(rawValue)) {
return rawValue
}
const r: Ref<any> = {
__v_isRef: true,
_rawValue: rawValue,
_value: shallow ? rawValue : toReactive(rawValue),
get value() {
track(r, TrackOpTypes.GET, 'value')
return r._value
},
set value(newVal) {
if (hasChanged(r._rawValue, newVal)) {
r._rawValue = newVal
r._value = shallow ? newVal : toReactive(newVal)
trigger(r, TriggerOpTypes.SET, 'value', newVal)
}
}
}
return r
}
关键点解析
-
createRef
函数:-
createRef
是ref
的实际实现函数。它接受两个参数:rawValue
(原始值)和shallow
(是否浅层响应式)。 - 如果传入的
rawValue
已经是一个ref
,则直接返回该ref
。 - 创建一个
r
对象,该对象具有以下属性:-
__v_isRef
: 标记这是一个ref
。 -
_rawValue
: 存储原始值。 -
_value
: 存储响应式值。如果shallow
为false
,则将原始值转换为响应式对象(使用toReactive
)。 -
get value
和set value
方法用于访问和设置value
属性。
-
-
-
get value
方法:- 当访问
r.value
时,会调用track
函数来追踪依赖关系。这使得 Vue 能够知道哪些组件或计算属性依赖于这个ref
。 - 返回
_value
属性的值。
- 当访问
-
set value
方法:- 当设置
r.value
时,首先检查新值newVal
是否与_rawValue
不同。如果不同,则更新_rawValue
和_value
。 - 调用
trigger
函数来触发依赖的更新。这会导致所有依赖于这个ref
的组件或计算属性重新渲染或计算。
- 当设置
小结
- 对于基本数据类型,
ref
通过创建一个包含value
属性的对象来包装该值,从而使该值变得响应式。 -
get value
和set value
方法分别用于追踪依赖关系和触发更新。 -
toReactive
函数用于将值转换为响应式对象,对于基本数据类型,它不会做任何特殊处理,只是返回原值。
通过这种方式,Vue 3 能够有效地管理基本数据类型的响应式变化。
2.toReactive
在 Vue 3 中,toReactive
函数用于将一个值转换为响应式对象。toReactive
主要通过 reactive
函数来实现,而 reactive
函数的核心是使用 Proxy
对象来拦截对对象属性的访问和修改,从而实现响应式。
源码位置
toReactive
和 reactive
的定义位于 @vue/reactivity
包中的 src/reactive.ts
文件中。
源码分析
toReactive
函数
// @vue/reactivity/src/reactive.ts
import { isObject, hasOwn, isFunction } from '@vue/shared'
import { reactiveMap, readonlyMap } from './baseHandlers'
import { ReactiveFlags, Target, TargetWithMemo } from './reactive'
import { EffectScope, recordEffectScope } from './effectScope'
import { toRaw, toReactive } from './reactive'
export function toReactive<T>(value: T): T {
return isObject(value) ? reactive(value) : value
}
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T> {
// 如果目标已经是响应式对象,直接返回
if (isReactive(target)) {
return target
}
// 如果目标是只读对象,返回只读版本
if (isReadonly(target)) {
return readonly(target)
}
// 创建响应式代理
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
createReactiveObject
函数
function createReactiveObject(
target: Target,
isShallow: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// 查找缓存的代理对象
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 创建新的代理对象
const proxy = new Proxy(target, collectionHandlers)
proxyMap.set(target, proxy)
return proxy
}
关键点解析
-
toReactive
函数:-
toReactive
函数首先检查传入的值是否是对象。如果是对象,调用reactive
函数将其转换为响应式对象;否则,直接返回原值。 - 这意味着对于基本数据类型(如数字、字符串等),
toReactive
直接返回原值,不会进行任何包装或转换。
-
-
reactive
函数:-
reactive
函数首先检查传入的目标是否已经是响应式对象。如果是,直接返回该对象。 - 如果目标是只读对象,返回只读版本。
- 否则,调用
createReactiveObject
函数创建一个新的响应式代理对象。
-
-
createReactiveObject
函数:-
createReactiveObject
函数首先检查传入的目标是否是对象。如果不是对象,直接返回原值。 - 检查是否有缓存的代理对象。如果有,返回缓存的代理对象。
- 否则,使用
Proxy
创建一个新的代理对象,并将其缓存到proxyMap
中。
-
处理基本数据类型和引用数据类型
-
基本数据类型:
- 对于基本数据类型(如数字、字符串等),
toReactive
直接返回原值,不会进行任何包装或转换。这是因为基本数据类型本身不能通过Proxy
进行拦截,所以没有必要对其进行响应式处理。
- 对于基本数据类型(如数字、字符串等),
-
引用数据类型:
- 对于引用数据类型(如对象、数组等),
toReactive
会调用reactive
函数,reactive
函数会进一步调用createReactiveObject
函数。 -
createReactiveObject
使用Proxy
创建一个新的代理对象,该代理对象会拦截对目标对象属性的访问和修改,从而实现响应式。
- 对于引用数据类型(如对象、数组等),
小结
-
toReactive
函数通过reactive
函数将引用数据类型转换为响应式对象。 - 对于基本数据类型,
toReactive
直接返回原值,不进行任何包装或转换。 -
reactive
函数使用Proxy
创建响应式代理对象,从而实现对引用数据类型的响应式管理。
通过这种方式,Vue 3 能够高效地管理和追踪复杂数据结构的变化,同时保持对基本数据类型的简洁处理。