概要
本文通过抽取Vue3 reactive源码中的次要局部,来了解其响应式object
的实现形式。本文源码基于这个入口文件:github reactive.ts
reactive()
源码地位:github reactive.ts
export function reactive(target) { return createReactiveObject( target, false, mutableHandlers, mutableCollectionHandlers )}
reactive
外部调用了createReactiveObject
函数,并传入mutableHandlers
作为Proxy的handler
createReactiveObject()
源码地位:github reactive.ts
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) { const proxy = new Proxy( target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers ) return proxy}
可见reactive
的实质,是将一般的object
转换成了Proxy
。而Proxy
只反对非空的object
,所以reactive
函数也只能用于object
。
const handler = {}new Proxy({}, handler) // 失常运行new Proxy(1, handler) // Cannot create proxy with a non-object as target or handler
如果用于原始类型的响应式,应该应用Vue3提供的ref
函数。
mutableHandlers
源码地位:github baseHandlers.ts
mutableHandlers
和mutableCollectionHandlers
用于new Proxy
的第二个参数,是reactive
函数最外围的逻辑,重点关注其get
和set
属性。
export const mutableHandlers = { get: createGetter(), set: createSetter(), deleteProperty, has, ownKeys,}
createGetter()
源码地位:github baseHandlers.ts
function createGetter(isReadonly = false, shallow = false) { return function get(target: Target, key: string | symbol, receiver: object) { // 外面代码比拟长,上面拆成几局部了解 // part 1 - 解决外部变量 // part 2 - 解决数组 // part 3 - 解决其余状况 }}
part 1 - 解决外部变量
// ts语法const enum ReactiveFlags { SKIP = '__v_skip', IS_REACTIVE = '__v_isReactive', IS_READONLY = '__v_isReadonly', IS_SHALLOW = '__v_isShallow', RAW = '__v_raw'}if (key === ReactiveFlags.IS_REACTIVE) { return !isReadonly} else if (key === ReactiveFlags.IS_READONLY) { return isReadonly} else if (key === ReactiveFlags.IS_SHALLOW) { return shallow} else if ( key === ReactiveFlags.RAW && receiver === (isReadonly ? shallow ? shallowReadonlyMap : readonlyMap : shallow ? shallowReactiveMap : reactiveMap ).get(target)) { return target}
应用reactive()
生成的object,都蕴含ReactiveFlags
对应的属性,能够输入看下成果:RunJS Demo。
应用shallowReactive()
生成的object,只有根结点的属性是响应式的,这和Vue2中的响应式变量的个性是一样的。参见这个示例:Vue3 shallowReactive
part 2 - 解决数组
const targetIsArray = isArray(target)if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) { return Reflect.get(arrayInstrumentations, key, receiver)}
Proxy为数组,且调用数组原型办法时,其实是先get
到办法名,再用这个办法去set
相应的值,比方上面的代码:
const arr = [{count: 1}, {count: 2}]const proxyArray = new Proxy(arr, { get(target, prop, receiver) { console.log(`get ${prop}`) return Reflect.get(target, prop, receiver) }, set(target, prop, receiver) { console.log(`set ${prop}`) return Reflect.set(target, prop, receiver) }})proxyArray.push({count: 3})
// 顺次输入为
get pushget lengthset 2set length
理论运行成果见:Javascript Proxy an array
part 3 - 解决其余状况
const res = Reflect.get(target, key, receiver)if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) { return res}if (!isReadonly) { track(target, TrackOpTypes.GET, key)}if (shallow) { return res}if (isRef(res)) { // ref unwrapping - skip unwrap for Array + integer key. return targetIsArray && isIntegerKey(key) ? res : res.value}if (isObject(res)) { // Convert returned value into a proxy as well. we do the isObject check // here to avoid invalid value warning. Also need to lazy access readonly // and reactive here to avoid circular dependency. return isReadonly ? readonly(res) : reactive(res)}return res
用reactive
初始化的变量中,如果蕴含ref
类型的值,则取值时不须要带上.value
。例如:
const count = ref(0)const state = reactive({count: count})// 以下两行,都能取到count值console.log(count.value)console.log(state.count)
理论运行成果见:Vue3 reactive object with ref
createSetter()
源码地位:github baseHandlers.ts
function createSetter(shallow = false) { return function set( target: object, key: string | symbol, value: unknown, receiver: object ): boolean { // part 1 - 赋值 // part 2 - 触发事件 }}
part 1 - 赋值
let oldValue = (target as any)[key]if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) { return false}if (!shallow) { if (!isShallow(value) && !isReadonly(value)) { oldValue = toRaw(oldValue) value = toRaw(value) } if (!isArray(target) && isRef(oldValue) && !isRef(value)) { oldValue.value = value return true }} else { // in shallow mode, objects are set as-is regardless of reactive or not}const hadKey = isArray(target) && isIntegerKey(key)? Number(key) < target.length: hasOwn(target, key)const result = Reflect.set(target, key, value, receiver)
part 2 - 解发事件
// don't trigger if target is something up in the prototype chain of originalif (target === toRaw(receiver)) { if (!hadKey) { trigger(target, TriggerOpTypes.ADD, key, value) } else if (hasChanged(value, oldValue)) { trigger(target, TriggerOpTypes.SET, key, value, oldValue) }}return result
只有新增(TriggerOpTypes.ADD
)和设置新值(TriggerOpTypes.SET
)的时候,才会触发(trigger)订阅过的事件
结语
原文链接:https://runjs.work/projects/a082a7c4e4b748a8
原文示例放弃更新。