blog-md
blog-md copied to clipboard
vue3源码解析 reactive类型
reactive
makeMap
先看个工具函数:
// Make a map and return a function for checking if a key
// is in that map.
//
// IMPORTANT: all calls of this function must be prefixed with /*#__PURE__*/
// So that rollup can tree-shake them if necessary.
export function makeMap(
str: string,
expectsLowerCase?: boolean
): (key: string) => boolean {
const map: Record<string, boolean> = Object.create(null)
const list: Array<string> = str.split(',')
for (let i = 0; i < list.length; i++) {
map[list[i]] = true
}
return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val]
}
makeMapk可以创建一个map存储字符串中提到的各种类型,来确定某种类型是否支持。
WeakMap
// WeakMaps that store {raw <-> observed} pairs.
const rawToReactive = new WeakMap<any, any>()
const reactiveToRaw = new WeakMap<any, any>()
const rawToReadonly = new WeakMap<any, any>()
const readonlyToRaw = new WeakMap<any, any>()
正常情况下是需要用两个map去做双向索引的,保存raw和reactive的相互指向关系。
这里使用了WeakMap,相比map可以解决弱引用下的垃圾回收问题。
reactive
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (readonlyToRaw.has(target)) {
return target
}
return createReactiveObject(
target,
rawToReactive,
reactiveToRaw,
mutableHandlers,
mutableCollectionHandlers
)
}
// Return a reactive-copy of the original object, where only the root level
// properties are reactive, and does NOT unwrap refs nor recursively convert
// returned properties.
export function shallowReactive<T extends object>(target: T): T {
return createReactiveObject(
target,
rawToReactive,
reactiveToRaw,
shallowReactiveHandlers,
mutableCollectionHandlers
)
}
reactive是composition的核心api,用来创建target对象的Proxy。
reactive和shallowReactive区别在于是否是深度响应式。
通过调用createReactiveObject来创建不同的Proxy对象。
createReactiveObject
function createReactiveObject(
target: unknown,
toProxy: WeakMap<any, any>,
toRaw: WeakMap<any, any>,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// target already has corresponding Proxy
let observed = toProxy.get(target)
if (observed !== void 0) {
return observed
}
// target is already a Proxy
if (toRaw.has(target)) {
return target
}
// only a whitelist of value types can be observed.
if (!canObserve(target)) {
return target
}
const handlers = collectionTypes.has(target.constructor)
? collectionHandlers
: baseHandlers
observed = new Proxy(target, handlers)
toProxy.set(target, observed)
toRaw.set(observed, target)
return observed
}
首先判断传入的target必须是一个对象,否则无法创建Proxy,基本类型使用Ref就好了。
接下来判断target的Proxy是否已经存在,以及是不是已经是个Proxy对象了。
这里使用void 0代替undefined,压缩后更短,避免undefined被重写,但是我很好奇为何不信任压缩工具嘞。
const canObserve = (value: any): boolean => {
return (
!value._isVue &&
!value._isVNode &&
isObservableType(toRawType(value)) &&
!rawValues.has(value) &&
!Object.isFrozen(value)
)
}
然后判断是否可以observe,只有白名单里的才可以,通常情况下没有被vue记录过,或者非vue实例、非vnode这种内部结点才可以被观测。
针对set map weakmap weakset
使用特殊的Proxy handler,其余绝大部分场景使用baseHandler来处理proxy行为。
mutableHandler
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
这几个proxy handler
没有太大区别,我们直接看最常用的mutableHandlers。
这个handler重写了这几个常用的属性,一个一个来看。
getter
const get = /*#__PURE__*/ createGetter()
function createGetter(isReadonly = false, shallow = false) {
return function get(target: object, key: string | symbol, receiver: object) {
const targetIsArray = isArray(target)
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
const res = Reflect.get(target, key, receiver)
if (isSymbol(key) && builtInSymbols.has(key)) {
return res
}
if (shallow) {
!isReadonly && track(target, TrackOpTypes.GET, key)
return res
}
if (isRef(res)) {
if (targetIsArray) {
!isReadonly && track(target, TrackOpTypes.GET, key)
return res
} else {
// ref unwrapping, only for Objects, not for Arrays.
return res.value
}
}
!isReadonly && track(target, TrackOpTypes.GET, key)
return isObject(res)
? isReadonly
? // need to lazy access readonly and reactive here to avoid
// circular dependency
readonly(res)
: reactive(res)
: res
}
}
/*#__PURE__*/
这个标志会告诉压缩工具当前的变量或者函数调用是没有副作用的,没用到的可以直接移除,实现更完善的的tree-shaking。
createGetter中对数组做了特殊处理,忽略了symbol属性的依赖追踪,对Object中的元素自动展开,但是忽略数组中的元素展开。
track和trigger是用来做依赖收集和触发的,后面详细分析。
arrayInstrumentations
vue3 arrayInstrumentations解析 专门写了一下详细分析。
循环依赖
前面的都比较常规,最后这几行看一下 : 当Object对象中的某个属性仍然为object时,需要lazy处理来避免循环依赖。
解决循环依赖有几种方式,引入中间人、延迟处理、缓存等等,对Proxy来说他只能劫持第一层的getter,如果有嵌套对象(如a.b.c
)的话,Proxy本身并不能解决问题,如果框架想直接递归遍历的话,性能和边界条件都会出问题。
这里使用lazy处理直接返回一个reactive的object,访问a.b
的时候只会触发b这一层的track;访问到a.b.c
的时候,框架会依次触发b层track,然后给b这个对象进行reactive处理,这样再访问b.c
的时候,因为已经被reactive化,此时的c
也进行了track, 就可以避开上面的两个问题。
setter
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
const oldValue = (target as any)[key]
if (!shallow) {
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 = hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
if (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
}
}
setter相对简单很多,不用多讲。