vue3 中的响应式 reactivity(一)
vue3源码分析太多,这里主要做一个学习笔记,也可以说是借鉴内容,兼听则明嘛。
1. ref
先看我们常用的ref函数
ref() --- ref.ts
export function ref(value?: unknown) {
return createRef(value, false)
}
回调一个createRef 函数
createRef() --- ref.ts
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
看样子shallow是一个标识,到底是什么呢😅😅,看来ref实际是调用一个RefImpl类
class RefImpl --- ref.ts
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
// 从构造函数可以看出 shallow 是一个用作标识是否 只是浅层调用的, 默认false,(默认会递归调用,全部进行响应式处理)
constructor(
value: T,
public readonly __v_isShallow: boolean,
) {
this._rawValue = __v_isShallow ? value : toRaw(value)
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
trackRefValue(this); // 依赖跟踪, 进行Dep操作
return this._value
}
set value(newVal) {
const useDirectValue =
this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
newVal = useDirectValue ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = useDirectValue ? newVal : toReactive(newVal)
triggerRefValue(this, DirtyLevels.Dirty, newVal)
}
}
}
toRaw() --- reactive.ts
toRaw() 可以返回由 reactive()、readonly()、shallowReactive() 或者 shallowReadonly() 创建的代理对应的原始对象。
export function toRaw<T>(observed: T): T {
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
}
> toRaw 用于返回对象的原始值,如果 observed 是一个响应式代理对象,那就通过 ReactiveFlags.RAW 拿到其原始值,否则返回其本身:
// 官网示例
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true
triggerRefValue() --- ref.ts
看着很复杂, 实际就是判断ref对象是否有 dep数组,有直接触发triggerEffects(dep)😅
export function triggerRefValue(
ref: RefBase<any>,
dirtyLevel: DirtyLevels = DirtyLevels.Dirty,
newVal?: any,
) {
ref = toRaw(ref)
const dep = ref.dep
if (dep) {
triggerEffects(
dep,
dirtyLevel,
__DEV__
? {
target: ref,
type: TriggerOpTypes.SET,
key: 'value',
newValue: newVal,
}
: void 0,
)
}
}
triggerEffects() --- effect.ts
export function triggerEffects(
dep: Dep,
dirtyLevel: DirtyLevels,
debuggerEventExtraInfo?: DebuggerEventExtraInfo,
) {
pauseScheduling()
// 依次执行 effect 队列中的 dep
// dep 中的 deps 数组存了该依赖的所有函数,会在此时被依次调用。(比如说watch, template中的更新都会存在deps里)
for (const effect of dep.keys()) {
// dep.get(effect) is very expensive, we need to calculate it lazily and reuse the result
let tracking: boolean | undefined
if (
effect._dirtyLevel < dirtyLevel &&
(tracking ??= dep.get(effect) === effect._trackId)
) {
effect._shouldSchedule ||= effect._dirtyLevel === DirtyLevels.NotDirty
effect._dirtyLevel = dirtyLevel
}
if (
effect._shouldSchedule &&
(tracking ??= dep.get(effect) === effect._trackId)
) {
if (__DEV__) {
effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo))
}
effect.trigger()
if (
(!effect._runnings || effect.allowRecurse) &&
effect._dirtyLevel !== DirtyLevels.MaybeDirty_ComputedSideEffect
) {
effect._shouldSchedule = false
if (effect.scheduler) {
queueEffectSchedulers.push(effect.scheduler)
}
}
}
}
resetScheduling()
}
2. reactive
类似ref, 我们先从常用的reactive() 函数入手
reactive() --- reactive.ts
跟ref一模一样有没有,直接返回一个createReactiveObject的初始函数(函数柯里化),用户只用调一个参数就行
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap,
)
}
createReactiveObject() --- reactive.ts
其实就带使用es6的proxy
,其中核心handler为 collectionHandlers
和 baseHandlers
, 看看上面的create函数,是不是传了两个handler,mutableHandlers
, mutableCollectionHandlers
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>,
) {
// 只接收引用类型
if (!isObject(target)) {
if (__DEV__) {
warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// target already has corresponding Proxy
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 只支持 Object Array Set WeakSet Map WeakMap only specific value types can be observed.
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 前面一大堆东西只为这个, 其中Set Map WeakSet WeakMap 类型为collection
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
)
// 存到proxyMap中,防止重复proxy
proxyMap.set(target, proxy)
return proxy
}
class MutableReactiveHandler --- baseHandlers.ts
最常用,数组和普通对象的handler
export const mutableHandlers: ProxyHandler<object> = new MutableReactiveHandler()
get 处理 大段代码警告🥵
康康源码先
class BaseReactiveHandler implements ProxyHandler<Target> {
constructor(
protected readonly _isReadonly = false,
protected readonly _isShallow = false,
) {}
get(target: Target, key: string | symbol, receiver: object) {
const isReadonly = this._isReadonly,
isShallow = this._isShallow
// ReactiveFlags 是在reactive中声明的枚举值,如果key是枚举值则直接返回对应的布尔值
// 如果为raw 直接返回 target
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.IS_SHALLOW) {
return isShallow
} else if (key === ReactiveFlags.RAW) {
if (
receiver ===
(isReadonly
? isShallow
? shallowReadonlyMap
: readonlyMap
: isShallow
? shallowReactiveMap
: reactiveMap
).get(target) ||
// receiver is not the reactive proxy, but has the same prototype
// this means the reciever is a user proxy of the reactive proxy
Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
) {
return target
}
// early return undefined
return
}
// 这里就是对数组方法的特殊处理
const targetIsArray = isArray(target)
if (!isReadonly) {
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
if (key === 'hasOwnProperty') {
return hasOwnProperty
}
}
// 获取返回值
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 (isShallow) {
return res
}
// 如果是ref, 不做递归reactive处理
// 如果是arr[1]这种的就不解包,直接返回 res ,其他返回res.value 解除.value包装
if (isRef(res)) {
// ref unwrapping - skip unwrap for Array + integer key.
return targetIsArray && isIntegerKey(key) ? res : res.value
}
// 如果值是引用类型,递归调用reactive
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,实现深层响应式。二是track依赖
set 处理 大段代码警告🥵
class MutableReactiveHandler extends BaseReactiveHandler {
constructor(isShallow = false) {
super(false, isShallow)
}
set(
target: object,
key: string | symbol,
value: unknown,
receiver: object,
): boolean {
let oldValue = (target as any)[key]
if (!this._isShallow) {
const isOldValueReadonly = isReadonly(oldValue)
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue)
value = toRaw(value)
}
// 不是数组, 旧值为ref 新值不为ref 直接赋值
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
if (isOldValueReadonly) {
return false
} else {
oldValue.value = value
return true
}
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
// 浅层模式下,不修改对象
}
// 是否为当前对象的key。如果是数组,是否为数组长度内的值
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: 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)) {
// trigger派发通知更新, 新值对象用add,旧值对象要set,
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
核心为set, set的重点是,每次修改都会进行trigger操作
小结 reactive
从上面的代码看下来,handler的目的就是为了对被代理的对象进行 track 和 triiger处理
3. reactiveEffect
阐述了响应式是如何实现,其中核心为track 和 trigger两个函数。
track() --- reactiveEffect.ts
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (shouldTrack && activeEffect) {
// 把需要跟踪的对象放到 depsMap 里 --- WeakMap
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
// depMaps里存放的是dep --- Map,key是 被track 的对象
// dep 里存放的是执行的, key值 是 被track的对象 key
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = createDep(() => depsMap!.delete(key))))
}
trackEffect(
activeEffect,
dep,
__DEV__
? {
target,
type,
key,
}
: void 0,
)
}
}
可以看出来,track的操作都是为了trackEffect🥶
课外知识 WeakMap,WeakMap的特点如下:
- key只能是Object,除了null之外,typeof obj === 'object' 的都可以当做key。
- WeakMap没有遍历的方法。
- WeakMap对key是弱引用,即垃圾回收机制不将该引用考虑在内 (gc机制为 浏览器 定期回收 不存在引用的的目标)
trigger() --- reactiveEffect.ts
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) {
// 只有被tracked 的 才会触发trigger
// never been tracked
return
}
let deps: (Dep | undefined)[] = []
if (type === TriggerOpTypes.CLEAR) {
// set和map的 clear 操作,depsMap中所有dep都要triiger
deps = [...depsMap.values()]
} else if (key === 'length' && isArray(target)) {
// eg:arr = [1,2,3,4], 操作为arr.lenght = 2,
// target 为arr ,key 为 length, newval 为 2
// 只对 arr.length, arr[2], arr[3] 的 dep 推入deps
const newLength = Number(newValue)
depsMap.forEach((dep, key) => {
if (key === 'length' || (!isSymbol(key) && key >= newLength)) {
deps.push(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
// key存在时,推入depsMap的deps
if (key !== void 0) {
deps.push(depsMap.get(key))
}
// also run for iteration key on ADD | DELETE | Map.SET
// 对set 和 map 进行特殊处理
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
deps.push(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
deps.push(depsMap.get(ITERATE_KEY))
}
break
}
}
// 暂停调用栈
pauseScheduling()
// 对deps队列中的dep进行 triggerEffects 操作
for (const dep of deps) {
if (dep) {
triggerEffects(
dep,
DirtyLevels.Dirty,
__DEV__
? {
target,
type,
key,
newValue,
oldValue,
oldTarget,
}
: void 0,
)
}
}
// 重置调用栈
resetScheduling()
}
同理嗷,trigger的操作是为了 triggerEffects
总结
本文以 ref()
和 reactive()
函数为切入点,对vue3响应式原理进行分析,主要分析了reactivity
目录下的 ref.ts
reactive.ts
baseHandler.ts
文件,ref在处理引用对象的时候其实还是调用了reactive方法,所以直接ref({})
跟reactive({})
区别不大,但是reactive有一个解包的过程,个人更喜欢reactive()
。
vue3源码的函数调用一层套一层,让人看了欲罢不能。(终极套娃,一层套一层🥵)。
无论是ref 还是 reactive 目前都分析到trackEffect()
、triggerEffects()
两个函数隶属于effect.ts
,还有一些一笔带过的暂停调用栈和重置调用栈pauseScheduling() resetScheduling()
,且看reactivity(二)。