reactive 与 ref 源码解析
一、reactive
1、reactive 定义及用法
reactive 返回一个对象的响应式代理。
<template>
<div>{{ state.count }}</div>
</template>
<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
console.log(state) // Proxy { count: 0 }
console.log(state.count) // 0
state.count++
console.log(state.count) // 1
</script>
2、reactive 源码解析
2-1、reactive
reactive 接受一个对象,判断是否只读,如果不是只读则调用 createReactiveObject
返回 Proxy 响应式对象:
// https://github.com/vuejs/core/blob/main/packages/reactivity/src/reactive.ts#L89
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
// 如果试图去观察一个只读的代理对象,会直接返回只读版本
if (isReadonly(target)) {
return target
}
// 创建一个代理对象并返回
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
2-2、createReactiveObject
createReactiveObject 接收 5 个参数:
- target:目标对象,想要生成响应式的原始对象;
- isReadonly:生成的代理对象是否只读;
- baseHandlers:生成代理对象的 handler 参数,当 target 类型是 Array 或 Object 时使用该 handler;
- collectionHandlers:当 target 类型是 Map、Set、WeakMap、WeakSet 时使用该 handler;
- proxyMap:存储生成代理对象后的 Map 对象。
// https://github.com/vuejs/core/blob/main/packages/reactivity/src/reactive.ts#L181
function createReactiveObject(
target: Target,
isReadonly: 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
}
// 如果目标已经是一个代理,直接返回
// 除非对一个响应式对象执行 readonly
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// 如果目标已经存在对应的代理对象
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 只有白名单里的类型才能被创建响应式对象
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
可以看到 createReactiveObject 会检验目标对象类型,然后通过 new Proxy()
生成一个代理对象,最后使用传入的 proxyMap 缓存并返回该对象。
在生成代理对象的过程中,会根据 target 目标对象类型决定使用哪个 Handlers,在 Handlers 中进行依赖的收集和触发。
- mutableHandlers:Array 或 Object 的 Handlers
- mutableCollectionHandlers:Map、Set、WeakMap、WeakSet 等集合的 Handlers
2-3、mutableHandlers
// https://github.com/vuejs/core/blob/main/packages/reactivity/src/baseHandlers.ts#L214
export const mutableHandlers: ProxyHandler<object> = {
// 属性读取操作的捕获器
get,
set,
deleteProperty,
has,
ownKeys
}
get
捕获器:属性读取操作的捕获器,通过 createGetter 函数创建。获取当前 key 对应的原始对象值,然后跟踪当前 key 的依赖,判断原始值是否为对象,是否需返回 reactive。- obj.pro
- array[index]
- array.indexOf()
- arr.length
- Reflect.get()
- Object.create(obj).foo
set
捕获器:通过 createSetter 函数创建,对比新旧值,判断是否新增属性,根据新旧值是否一致走不同的 trigger 逻辑。- obj.str=''
- arr[0]=1
- arr.length=2
- Reflect.set()
- Object.create(obj).foo = 'foo'
deleteProperty
捕获器:- delete obj.str
- Reflect.delete
- deleteProperty
has
捕获器:- for...in...
- key in obj
- Reflect.has()
ownKeys
捕获器- Object.keys()
- Object.getOwnPropertyNames()
- Object.getOwnPropertySymbols()
- Reflect.ownKeys()
2-4、mutableCollectionHandlers
// https://github.com/vuejs/core/blob/main/packages/reactivity/src/collectionHandlers.ts#L366
export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: /*#__PURE__*/ createInstrumentationGetter(false, false)
}
get 捕获器通过 createInstrumentationGetter 函数生成,点击查看详情。
2-5、reactive 全流程
reactive 通过 createReactiveObject 方法,校验目标对象类型,生成 Proxy 代理对象,并根据目标对象类型决定使用哪个 Handlers,在 Handlers 中通过拦截代理对象进行依赖的收集和触发。最后使用传入的 proxyMap 缓存并返回该代理对象。
二、ref
1、ref 定义及用法
ref 接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value。
<template>
<div>{{ count }}</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
console.log(count) // RefImpl { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
</script>
2、ref 源码解析
ref 接收一个任意类型的参数,并返回 createRef 的返回值。
// https://github.com/vuejs/core/blob/main/packages/reactivity/src/ref.ts#L80
export function ref(value?: unknown) {
return createRef(value, false)
}
2-1、createRef
createRef 接收 ref 的原始值以及一个 shallow 参数表明是否为浅层响应式,通过 isRef 判断是否为 rawValue,如果是则直接返回这个 ref 对象。否则返回一个新创建的 RefImpl 类的实例对象。
// https://github.com/vuejs/core/blob/main/packages/reactivity/src/ref.ts#L97
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
2-2、RefImpl 类
在 RefImpl 类中,_rawValue
用来存放 ref 的旧值,_value
用来存储 ref 的最新值。
当以 ref.value 的形式读取值时,会触发 getter 方法,通过 track 收集 ref 对象 value 的依赖,收集完成返回该 ref 的值。
当对 ref.value 进行修改时,会触发 setter 方法,对新旧 value 进行比较,如果不同则进行更新,然后通过 trigger 派发 ref 对象 value 的更新,触发依赖,让依赖该 ref 的副作用函数执行更新。
// https://github.com/vuejs/core/blob/main/packages/reactivity/src/ref.ts#L104
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
// 用来标识该对象是一个 ref 响应式对象的标记
public readonly __v_isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
// 获取原始值,如果是浅层响应式,则原始值是 value
// 否则为 value 的原始值
this._rawValue = __v_isShallow ? value : toRaw(value)
// 响应式数据,如果是浅层响应式,则为 value
// 否则转为 reactive(只有 Object 类型才会转为 reactive)
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
// 获取 new RefImpl() 的 value 前
// 会先调用 trackRefValue 进行依赖收集,并返回 this._value
trackRefValue(this)
return this._value
}
set value(newVal) {
const useDirectValue =
this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
newVal = useDirectValue ? newVal : toRaw(newVal)
// 当 newVal 与旧原始值不同时,触发依赖
if (hasChanged(newVal, this._rawValue)) {
// 更新原始值及响应式数据
this._rawValue = newVal
// 如果是深度响应式的 ref,则转 reactive 以达到深度响应式的效果
this._value = useDirectValue ? newVal : toReactive(newVal)
// 派发 value 更新
triggerRefValue(this, newVal)
}
}
}
点击查看 shallowRef、triggerRef、customRef 等原理
三、总结
reactive 通过 createReactiveObject
的方法,校验目标对象类型,生成 Proxy 代理对象,并根据目标对象类型在 Handlers 中进行依赖的收集和触发。最后缓存并返回生成的 Proxy 代理对象。
ref 在创建时返回一个类实例,在类实例中通过 get
和 set
进行依赖的收集与触发,完成响应式追踪。