前置
本文主要是对vue3中响应式effect源码的解读。
1、介绍effect
把对象转换成响应式对象,还需要依赖收集和触发更新,组成一个响应式系统。effect是响应式的核心,主要功能就是依赖收集和触发更新。被传入effect的函数称为副作用函数,这个函数就会在触发更新时执行。
什么是副作用函数:
函数内部使用到的响应式对象与effect建立映射关系。当这个响应式对象发生变化,这个effect能找到这个函数并执行。computed和watch的底层核心就是effect,所以传入这个两个api的函数也是副作用函数。
2、使用
const state = reactive({ age: 18, count:0 })
effect(() => {
console.log('age:', state.age)
})
setTimeout(() => {
state.age++
}, 1000)
setTimeout(() => {
state.count++
}, 2000)
// 打印结果:
// age: 18
// age: 19
结果:
1、第一次打印age: 18,因为effect默认执行一次。
2、一秒后修改第二次打印age: 19,因为state.age与effect建立映射,age这个属性被收集了。当age变化了触发更新,副作用函数执行。
3、两秒后没有打印,因为effect内没有使用count这个属性,所以没有依赖收集也就不会触发更新。
简化代码
创建effect
export let activeEffect: ReactiveEffect | undefined // 当前正在执行的effect
class ReactiveEffect<T = any> {
deps: Dep[] = [] // 收集effect中使用到的属性,依赖收集,记录哪些属性是在effect中使用的
parent: ReactiveEffect | undefined = undefined // 父级的effect
constructor(
public fn: () => T,
) {}
run() {
let parent: ReactiveEffect | undefined = activeEffect
try {
this.parent = activeEffect // 缓存父级的effect
activeEffect = this // 设置成正在执行的是当前effect
// 执行用户传入的副作用函数
return this.fn()
} finally {
// 执行完毕后还原activeEffect
activeEffect = this.parent
this.parent = undefined
}
}
}
// 1.effect
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
): ReactiveEffectRunner {
// 将用户的函数变成一个响应式的函数
const _effect = new ReactiveEffect(fn) ///2.创建响应式effect
// 3.执行run方法
if (!options || !options.lazy) {
// 是不是默认执行
_effect.run()
}
// 4.确保在外面调用runner时this指向的正确性
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
runner.effect = _effect
return runner
}
解读过程:
1、effect接收两个参数,用户传入的副作用函数和配置项
// 配置项
interface ReactiveEffectOptions {
lazy?: boolean // effecct不会立刻执行
scheduler?: EffectScheduler // 调度函数
scope?: EffectScope // effect作用域
allowRecurse?: boolean // 允许更新触发effect执行
onStop?: () => void // 终止函数stop触发
onTrack?: (event: DebuggerEvent) => void // 收集依赖触发
onTrigger?: (event: DebuggerEvent) => void // 触发更新触发
}
2、创建响应式effect
3、执行effect,run方法中的activeEffect是个全局变量,保存当前的effect,在响应式对象get或者set触发的时候,能从activeEffect拿到当前的effect
parent是用在嵌套effect中,保留父级的effect,实例如下
// 全局变量activeEffect
// 这是effect1
effect(()=>{
// 全局变量activeEffect = effect1
state.name;
// 这是effect2
effect(()=>{
// effect2缓存了上一个effect也就是effect1
// effect2.parent = activeEffect = effect1
// activeEffect = effect2
state.age
})
// effect2执行结束,后还原activeEffect为effec1
// activeEffect = effect2.parent = effect1
state.address;
})
收集依赖
//1.
function get(target: Target, key: string | symbol, receiver: object) {
// ...省略无关代码
// 取值
const res = Reflect.get(target, key, receiver)
// 如果是仅读的不需要做依赖收集
if (!isReadonly) {
track(target, TrackOpTypes.GET, key) // 收集依赖
}
// ...省略无关代码
return res
}
// 记录依赖关系
const targetMap = new WeakMap<any, KeyToDepMap>()
function track(target: object, type: TrackOpTypes, key: unknown) {
// 否开启依赖搜集 以及 当前这个属性是在effect中使用才收集
if (shouldTrack && activeEffect) {
// 2.
// 第一次用源对象去找一下有没有映射表
let depsMap = targetMap.get(target)
if (!depsMap) {
// 没有值说明没有维护过依赖,给源对象创建一个空的map映射表
targetMap.set(target, (depsMap = new Map()))
}
// 属性值查一下有没有映射表
let dep = depsMap.get(key)
if (!dep) {
// 没有key对应的映射,给属性值创建一个空的set映射表
depsMap.set(key, (dep = new Set()))
}
// 不用重复收集同个key
let shouldTrack = !dep.has(activeEffect)
// 没有重复的
if (shouldTrack) {
// 依赖记录effect 这里实现n对n
dep.add(activeEffect);
// dep = [effect, effect]
activeEffect.deps.push(dep); // 让effect记住dep,这样后续可以用于清理
}
}
}
解读过程:
1、这里参考reactive处get。即响应式对象获取值的时候,会判断是否依赖收集以及是否在effect中
2、依赖收集过程:记录依赖关系以及依赖收集过程,通过Map建立关联。
- 首先关联目标对象和被触发的属性,然后被触发的属性再记录当前的
activeEffect,这里记录的属性可以有多个Effect。 - 最后给当前的
Effect也就是activeEffect添加deps属性来记录有哪些属性(一个对象对应一个Map,Map里面一个key对应一个Set)。
过程如下:
const state = reactive({age: 18})
effect(()=>{
// effect1
state.age
})
effect(()=>{
// effect2
state.age
})
targetMap = {({ age: 18}): Map2 } 用一个Map来描述对象和key的关系
Map2 = {age: [effect1, effect2]} 描述key和effect的关系
结果如下
targetMap = {({ age: 18}): {age: [effect1, effect2]} }
总结:将属性和对应的effect维护成映射关系,后续属性变化可以触发对应的effect函数重新run
触发更新
// 1.
function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
// ...省略无关代码
// 查看是否有过这个key
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length // 数组访问索引在长度中
: hasOwn(target, key) // 对象有这个属性
const result = Reflect.set(target, key, value, receiver)
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
}
// 2.触发更新过程
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) {
// 从未收集过
// never been tracked
return
}
const effects = depsMap.get(key);
effects && effects.forEach(effect => {
// 当前正在执行和现在要执行的是同一个就屏蔽(比如在effect又修改同个值)
if (effect !== activeEffect) effect.run();
if (effects) {
effects = new Set(effects);
for (const effect of effects) {
if (effect !== activeEffect) {
if(effect.scheduler){ // 如果有调度函数则执行调度函数
effect.scheduler()
}else{
effect.run();
}
}
}
})
}
解读过程:
1、这里参考reactive处set。即响应式对象设置值的时候,会判断是新增的属性还是变化的属性
2、触发更新过程:
- 判断目标对象和触发的属性是否存在依赖收集。
- 将属性对应关联的一个或多个
effect调用scheduler或者run方法触发更新。
什么是scheduler?
trigger触发时,可以自己决定副作用函数执行方式
const state = reactive({ age: 18 });
const runner = effect(
() => {
console.log('runner');
},
{
scheduler() {
console.log('scheduler');
},
}
);
state.age ++;
// 打印结果
// runner 第一次默认运行effect.run()
// scheduler 第二次运行effect.scheduler()
源码解读
effect
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
): ReactiveEffectRunner {
if ((fn as ReactiveEffectRunner).effect) {
fn = (fn as ReactiveEffectRunner).effect.fn // 是否已经是effect,是effect获取对应的原函数
}
const _effect = new ReactiveEffect(fn) // 创建响应式effect
if (options) {
extend(_effect, options)
if (options.scope) recordEffectScope(_effect, options.scope)
}
if (!options || !options.lazy) {
// 是不是立即执行
_effect.run()
}
// this指向的正确性
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
runner.effect = _effect
return runner
}
ReactiveEffect
export class ReactiveEffect<T = any> {
active = true // 激活响应式effect
deps: Dep[] = [] // 收集effect中使用到的属性
parent: ReactiveEffect | undefined = undefined
computed?: ComputedRefImpl<T> // 标记computed
allowRecurse?: boolean
private deferStop?: boolean
onStop?: () => void
// dev only
onTrack?: (event: DebuggerEvent) => void
// dev only
onTrigger?: (event: DebuggerEvent) => void
constructor(
public fn: () => T, // 副作用函数
public scheduler: EffectScheduler | null = null, // 调度函数
scope?: EffectScope // 作用域对象
) {
recordEffectScope(this, scope) // 记录effect的作用域,可以灵活控制effect的执行
}
run() {
if (!this.active) {
return this.fn()
}
let parent: ReactiveEffect | undefined = activeEffect
let lastShouldTrack = shouldTrack
while (parent) {
// 防止死循环
if (parent === this) {
return
}
parent = parent.parent
}
try {
this.parent = activeEffect
activeEffect = this
shouldTrack = true
// 每次执行嵌套的effect函数时会递增,通过位操作的方式
trackOpBit = 1 << ++effectTrackDepth
if (effectTrackDepth <= maxMarkerBits) { // const maxMarkerBits = 30
// effect会记录依赖是否被追踪,这里初始化记录
initDepMarkers(this)
} else {
// 这里会清空当前的effect被之前依赖收集的映射
// 下面运行的时候会重新进行依赖收集,为了避免不需要的旧的依赖还被收集
cleanupEffect(this)
}
return this.fn()
} finally {
if (effectTrackDepth <= maxMarkerBits) {
// 处理依赖关系,之前的收集依赖现在没有收集,就需要清除
finalizeDepMarkers(this)
}
// 恢复嵌套
trackOpBit = 1 << --effectTrackDepth
activeEffect = this.parent
shouldTrack = lastShouldTrack
this.parent = undefined
if (this.deferStop) {
this.stop()
}
}
}
// 停止effect
stop() {
// 停止操作发生在当前的effect,会延迟执行
// stopped while running itself - defer the cleanup
if (activeEffect === this) {
this.deferStop = true
} else if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
// 当前effect设置不激活
this.active = false
}
}
}
dep.ts
// 依赖在之前被收集
export const wasTracked = (dep: Dep): boolean => (dep.w & trackOpBit) > 0
// 依赖在现在的effect中被收集
export const newTracked = (dep: Dep): boolean => (dep.n & trackOpBit) > 0
// 初始化依赖记录
export const initDepMarkers = ({ deps }: ReactiveEffect) => {
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].w |= trackOpBit // set was tracked
}
}
}
// 处理依赖关系
export const finalizeDepMarkers = (effect: ReactiveEffect) => {
const { deps } = effect
if (deps.length) {
let ptr = 0
for (let i = 0; i < deps.length; i++) {
const dep = deps[i]
if (wasTracked(dep) && !newTracked(dep)) {
// 之前的收集依赖现在没有收集,就需要清除
dep.delete(effect)
} else {
deps[ptr++] = dep
}
// clear bits
dep.w &= ~trackOpBit
dep.n &= ~trackOpBit
}
deps.length = ptr
}
}
track
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (shouldTrack && activeEffect) {
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = createDep()))
}
const eventInfo = __DEV__
? { effect: activeEffect, target, type, key }
: undefined
trackEffects(dep, eventInfo)
}
}
export const createDep = (effects?: ReactiveEffect[]): Dep => {
const dep = new Set<ReactiveEffect>(effects) as Dep
dep.w = 0 // 是否被收集
dep.n = 0 // 是否新收集的依赖
return dep
}
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
let shouldTrack = false
if (effectTrackDepth <= maxMarkerBits) {
if (!newTracked(dep)) {
// 这个依赖现在没有被收集,需要设置新依赖标识
dep.n |= trackOpBit // set newly tracked
// 这个依赖被收集了true,就不需要再收集shouldTrack=false
// 这个依赖没有被收集,就需要收集shouldTrack=true
shouldTrack = !wasTracked(dep)
}
} else {
// Full cleanup mode.
shouldTrack = !dep.has(activeEffect!)
}
// 依赖收集
if (shouldTrack) {
dep.add(activeEffect!)
activeEffect!.deps.push(dep)
if (__DEV__ && activeEffect!.onTrack) {
activeEffect!.onTrack(
extend(
{
effect: activeEffect!
},
debuggerEventExtraInfo!
)
)
}
}
}
trigger
// 迭代行为标识符
export const ITERATE_KEY = Symbol('iterate')
export const MAP_KEY_ITERATE_KEY = Symbol("Map key iterate")
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) {
// 从未收集过
// never been tracked
return
}
let deps: (Dep | undefined)[] = []
if (type === TriggerOpTypes.CLEAR) {
// 针对集合触发清理数据,所有的dep应该触发
// collection being cleared
// trigger all effects for target
deps = [...depsMap.values()]
} else if (key === 'length' && isArray(target)) {
// 修改的是长度
const newLength = Number(newValue)
depsMap.forEach((dep, key) => {
// 之前收集的长度大于现在的长度,需要重新收集
if (key === 'length' || key >= newLength) {
deps.push(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
deps.push(depsMap.get(key))
}
// 新增,删除和修改要分别判断
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
// 添加属性的时候,要触发迭代收集的key
// track(target, "iterate", isArray(target) ? "length" : ITERATE_KEY);
// 因为被收集的依赖是迭代操作for in会添加ITERATE_KEY
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
// 如果是map新增属性触发length更新
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:
// Map对象发生更改时,更新依赖
if (isMap(target)) {
deps.push(depsMap.get(ITERATE_KEY))
}
break
}
}
// 开发环境onTrigger回调的信息
const eventInfo = __DEV__
? { target, type, key, newValue, oldValue, oldTarget }
: undefined
// 依赖有值
if (deps.length === 1) {
// 有一个依赖
if (deps[0]) {
if (__DEV__) {
triggerEffects(deps[0], eventInfo)
} else {
triggerEffects(deps[0])
}
}
} else {
// 有多个依赖
const effects: ReactiveEffect[] = []
for (const dep of deps) {
if (dep) {
effects.push(...dep)
}
}
if (__DEV__) {
triggerEffects(createDep(effects), eventInfo)
} else {
triggerEffects(createDep(effects))
}
}
}
export function triggerEffects(
dep: Dep | ReactiveEffect[],
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// spread into array for stabilization
const effects = isArray(dep) ? dep : [...dep]
for (const effect of effects) {
if (effect.computed) {
triggerEffect(effect, debuggerEventExtraInfo)
}
}
for (const effect of effects) {
if (!effect.computed) {
triggerEffect(effect, debuggerEventExtraInfo)
}
}
}
function triggerEffect(
effect: ReactiveEffect,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
if (effect !== activeEffect || effect.allowRecurse) {
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
}
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
小结
- effect通过副作用函数和响应式数据组成响应式
- 响应式数据触发get方法,执行track,通过
targetMap将effect和收集的target和key建立映射关系。 - 响应式数据触发set方法,执行trigger,通过
target和key在targetMap中触发对应的effect。