reactive
:将一个JS对象转为具有响应式的proxy实例export function reactive(target: object) {
// 如果是只读数据,就直接返回
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
createReactiveObject
function createReactiveObject(
target: Target, // 源对象
isReadonly: boolean, // 是否只读
baseHandlers: ProxyHandler<any>, // 基本类型的handlers
collectionHandlers: ProxyHandler<any>, // 主要针对(set、map、weakSet、weakMap)的handlers
proxyMap: WeakMap<Target, any>
) {
// 如果不是一个对象,直接返回
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// 如果已经是响应式,直接返回
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// 如果目标对象已经存在代理,直接返回
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 如果类型值不是Object、Array、Map、Set、WeakMap、WeakSet的,直接返回
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 根据不同的类型值赋予不同的handlers,就是我之前图上画的分开处理
/*
把set、Map这种数据与基础数据分开处理,是因为Map、Set中存储的数据必须通过this进行访问
但是被proxy劫持后,this就变成了proxy,
所以需要特殊处理,把劫持方法进行重写
*/
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
baseHandler
这个文件我主要分析reactive的处理器对象mutableHandlers
// 对get set delete has onwKeys做了拦截处理
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
// 访问标志位时的逻辑处理
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (
key === ReactiveFlags.RAW &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
return target
}
const targetIsArray = isArray(target)
// 如果target是数组并且key属于一些数组的原始方法,即触发拦截hack
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
const res = Reflect.get(target, key, receiver)
// 如果key是symbol的内置方法,或者是原型对象,直接返回
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
// 只读对象不收集依赖,因为不会触发依赖更新
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// 浅层响应立即返回,不递归转化
if (shallow) {
return res
}
// 如果是ref对象(数组除外),返回真正的值,
if (isRef(res)) {
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
return shouldUnwrap ? res.value : res
}
if (isObject(res)) {
// 由于proxy只能代理一层,所以target[key]的值如果是对象,就继续对其进行代理
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
数组方法拦截hack: 响应式系统对数组的两种原生方法进行了hack
function createArrayInstrumentations() {
const instrumentations: Record<string, Function> = {}
;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
const method = Array.prototype[key] as any
instrumentations[key] = function(this: unknown[], ...args: unknown[]) {
// 这一步是为了取原始实例,因为当前的this是receiver
const arr = toRaw(this)
// 搜集依赖
for (let i = 0, l = this.length; i < l; i++) {
track(arr, TrackOpTypes.GET, i + '')
}
// 触发方法,如果没有找到对应的值,就取原始值再遍历
const res = method.apply(arr, args)
if (res === -1 || res === false) {
return method.apply(arr, args.map(toRaw))
} else {
return res
}
}
})
/*
因为改变数组长度的方法,执行期间会触发length的get和set
就回导致无限循环track和trigger
所以就用pauseTracking()禁用依赖收集,触发方法后,
再用resetTracking()恢复track
*/
;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
const method = Array.prototype[key] as any
instrumentations[key] = function(this: unknown[], ...args: unknown[]) {
pauseTracking()
const res = method.apply(this, args)
resetTracking()
return res
}
})
return instrumentations
}
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key]
if (!shallow) {
value = toRaw(value)
oldValue = toRaw(oldValue)
// 如果原来的值是ref,但新的值不是,则将新的值赋给oldValue.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)
// 判断receiver是当前对象的proxy实例,防止原型链上的proxy触发更新
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
}
}
// 主要对以下原生api进行了改写
const mutableInstrumentations: Record<string, Function> = {
get(this: MapTypes, key: unknown) {
return get(this, key)
},
get size() {
return size((this as unknown) as IterableCollections)
},
has,
add,
set,
delete: deleteEntry,
clear,
forEach: createForEach(false, false)
}
function get(
target: MapTypes,
key: unknown,
isReadonly = false,
isShallow = false
) {
// 获取原始值重新赋值给target
target = (target as any)[ReactiveFlags.RAW]
// 对target源对象和key进一步获取原始值
const rawTarget = toRaw(target)
const rawKey = toRaw(key)
// 包装后的key和原始key均进行依赖收集(track)
if (key !== rawKey) {
!isReadonly && track(rawTarget, TrackOpTypes.GET, key)
}
!isReadonly && track(rawTarget, TrackOpTypes.GET, rawKey)
const { has } = getProto(rawTarget)
// 获取对应的转换函数
const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
/*
如果源对象有key对应的属性,就通过原生get方法取到值,
并对该值进行响应式转换,返回转换后的响应式对象,
如果没有,就去key原始值中去查找
*/
if (has.call(rawTarget, key)) {
return wrap(target.get(key))
} else if (has.call(rawTarget, rawKey)) {
return wrap(target.get(rawKey))
} else if (target !== rawTarget) {
target.get(key)
}
}
// 对size属性做get拦截
function size(target: IterableCollections, isReadonly = false) {
target = (target as any)[ReactiveFlags.RAW]
// 获取size和获取数组的length类似,都用专门的key做依赖收集
!isReadonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
return Reflect.get(target, 'size', target)
}
function set(this: MapTypes, key: unknown, value: unknown) {
// 获取value和this上下文的原始值
value = toRaw(value)
const target = toRaw(this)
const { has, get } = getProto(target)
/*
判断源对象是否已经存在key对应的属性
1. 首先查找源对象是否已有key对应的属性
2. 如果没有,再查找key对应的原始值在源对象的属性是否存在
*/
let hadKey = has.call(target, key)
if (!hadKey) {
key = toRaw(key)
hadKey = has.call(target, key)
} else if (__DEV__) {
checkIdentityKeys(target, has, key)
}
const oldValue = get.call(target, key)
target.set(key, value)
// 触发依赖,新增属性和修改属性分开进行trigger
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
return this
}
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
// 如果已经是effect函数,取得原来的fn
if (isEffect(fn)) {
fn = fn.raw
}
const effect = createReactiveEffect(fn, options)
// 如果lazy为false,立即执行一次
if (!options.lazy) {
effect()
}
return effect
}
createReactiveEffect
:生成effect对象function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
// 没有激活,已经调用stop函数停止监听
if (!effect.active) {
return fn()
}
// 判断effectStack中有没有effect,避免递归循环,effectStack是一个全局effect栈
if (!effectStack.includes(effect)) {
// 清除effect依赖,保证当前effect的dep是最新且有效的
cleanup(effect)
try {
enableTracking() // 重新收集依赖
effectStack.push(effect)
activeEffect = effect
return fn()
} finally {
/*
track将依赖函数activeEffect添加到对应的dep中,
然后将activeEffect重置为上一个effect的值
*/
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect.id = uid++ // 自增ID
effect.allowRecurse = !!options.allowRecurse // 递归状态
effect._isEffect = true // 用于标识方法是不是effect
effect.active = true // 用于判断当前effect是否激活,有一个stop()来将它设为false
effect.raw = fn // effect的执行函数
effect.deps = [] // 用于收集依赖
effect.options = options // 创建effect传入的options
return effect
}
// targetMap是依赖管理中心,收集依赖和触发依赖都依托于这个Map数据
// 下面是targetMap的定义(target -> key -> dep)
// target: 监听的对象源
// key: 监听的键值
// dep:依赖函数
type Dep = Set<ReactiveEffect>
type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<any, KeyToDepMap>()
// 格式大致为
targetMap = {
target: {
key1: { fn1, fn2 }
key2: { fn1, fn2 }
}
}
/**
* @param {target} 目标对象
* @param {type} 收集类型
* @param {key} 需要收集依赖的key
*/
export function track(target: object, type: TrackOpTypes, key: unknown) {
// activeEffect为空,就表示当前没有依赖,就没必要做依赖收集了
if (!shouldTrack || activeEffect === undefined) {
return
}
// 获取当前依赖数据
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
// 如果当前数据中没有所属的依赖key,就重新设置一个
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
// 添加依赖函数
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
if (__DEV__ && activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
})
}
}
}
// 触发依赖
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
// 如果没有收集过依赖,直接返回
if (!depsMap) {
return
}
const effects = new Set<ReactiveEffect>()
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
// 避免重复触发依赖
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect)
}
})
}
}
if (type === TriggerOpTypes.CLEAR) {
// 在清空前,将对应的依赖全部添加到局部Set
depsMap.forEach(add)
} else if (key === 'length' && isArray(target)) {
// 当数组的length属性变化时触发
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
add(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
// 往相应队列添加依赖
if (key !== void 0) {
add(depsMap.get(key))
}
// also run for iteration key on ADD | DELETE | Map.SET
// 通过不同的TriggerOpTypes将depsMap的数据取出,添加到effects
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
add(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY))
}
break
}
}
const run = (effect: ReactiveEffect) => {
if (__DEV__ && effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
})
}
if (effect.options.scheduler) {
// 如果有调度属性,就通过scheduler处理执行
effect.options.scheduler(effect)
} else {
effect()
}
}
effects.forEach(run)
}