vue3响应式核心文章汇总:
vue3响应式核心之reactive源码详解
vue3响应式核心之effect源码详解
上篇vue3响应式核心之reactive源码详解详细讲解了reactive的原理和实现源码,本篇文章将讲解effect源码。
通常情况下我们是不会直接使用effect的,因为effect是一个底层的API,在我们使用Vue3的时候Vue默认会帮我们调用effect。 effect翻译为作用,意思是使其发生作用,这个使其的其就是我们传入的函数,所以effect的作用就是让我们传入的函数发生作用,也就是执行这个函数。 执行过程简图如下:
接下来先通过例子了解effect的基本用法,然后再去了解原理。
一、effect用法
1、基本用法
const obj = reactive({count: 1})
const runner = effect(() => {
console.log(obj.count)
})
obj.count++
结果会先打印1, 然后在obj.count++
之后打印出2。
流程简图如下:
运行effect(fun)
// 先执行
fun() // 打印出1
const runner = new ReactiveEffect(fn)
return runner
runner: {
run() {
this.fun() //执行fun
},
stop() {
}
}
console.log(obj.count)
track依赖收集 结构如下:
obj.count++
触发依赖,执行runner.run(), 实际运行的是
() => {
console.log(obj.count)
}
所以又打印出2
2、lazy属性为true
此值为 true 时,只有在第一次手动调用 runner 后,依赖数据变更时,才会自动执行 effect 的回调,可以理解为 effect 的是在手动调用 runner 后才首次执行
const obj = reactive({count: 1})
const runner = effect(() => {
console.log(obj.count)
}, {
lazy: true
})
runner()
obj.count++
只会打印出2
原因是effect源码中有如下逻辑:
3、options中包含onTrack
let events = []
const onTrack = (e) => {
events.push(e)
}
const obj = reactive({ foo: 1, bar: 2 })
const runner = effect(
() => {
console.log(obj.foo)
},
{ onTrack }
)
console.log('runner', runner)
obj.foo++
console.log("events", events)
看下events的打印结果:
[
{
effect: runner, // effect 函数的返回值
target: toRaw(obj), // 表示的是哪个响应式数据发生了变化
type: TrackOpTypes.GET, // 表示此次记录操作的类型。 get 表示获取值
key: 'foo'
}
]
二、源码分析
1、effect方法的实现
// packages/reactivity/src/effect.ts
export interface ReactiveEffectOptions extends DebuggerOptions {
lazy?: boolean
scheduler?: EffectScheduler
scope?: EffectScope
allowRecurse?: boolean
onStop?: () => void
}
export function effect<T = any>(
fn: () => T, // 副作用函数
options?: ReactiveEffectOptions // 结构如上
): ReactiveEffectRunner {
// 如果 fn 对象上有 effect 属性
if ((fn as ReactiveEffectRunner).effect) {
// 那么就将 fn 替换为 fn.effect.fn
fn = (fn as ReactiveEffectRunner).effect.fn
}
// 创建一个响应式副作用函数
const _effect = new ReactiveEffect(fn)
if (options) {
// 将配置项合并到响应式副作用函数上
extend(_effect, options)
// 如果配置项中有 scope 属性(该属性的作用是指定副作用函数的作用域)
if (options.scope) recordEffectScope(_effect, options.scope)
}
if (!options || !options.lazy) { // options.lazy 不为true
_effect.run() // 执行响应式副作用函数 首次执行fn()
}
// _effect.run作用域绑定到_effect
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
// 将响应式副作用函数赋值给 runner.effect
runner.effect = _effect
return runner
}
核心代码:
创建一个响应式副作用函数const _effect = new ReactiveEffect(fn)
,其运行结果如下:
非lazy状态执行响应式副作用函数_effect.run()
if (!options || !options.lazy) { // options.lazy 不为true
_effect.run() // 执行响应式副作用函数 首次执行fn()
}
_effect.run
作用域绑定到_effect
// _effect.run作用域绑定到_effect
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
返回副作用函数runner
2、ReactiveEffect函数源码
export class ReactiveEffect<T = any> {
active = true
deps: Dep[] = [] // 响应式依赖项的集合
parent: ReactiveEffect | undefined = undefined
computed?: ComputedRefImpl<T>
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
) {
// 记录当前 ReactiveEffect 对象的作用域
recordEffectScope(this, scope)
}
run() {
// 如果当前 ReactiveEffect 对象不处于活动状态,直接返回 fn 的执行结果
if (!this.active) {
return this.fn()
}
// 寻找当前 ReactiveEffect 对象的最顶层的父级作用域
let parent: ReactiveEffect | undefined = activeEffect
let lastShouldTrack = shouldTrack // 是否要跟踪
while (parent) {
if (parent === this) {
return
}
parent = parent.parent
}
try {
// 记录父级作用域为当前活动的 ReactiveEffect 对象
this.parent = activeEffect
activeEffect = this // 将当前活动的 ReactiveEffect 对象设置为 “自己”
shouldTrack = true // 将 shouldTrack 设置为 true (表示是否需要收集依赖)
// effectTrackDepth 用于标识当前的 effect 调用栈的深度,执行一次 effect 就会将 effectTrackDepth 加 1
trackOpBit = 1 << ++effectTrackDepth
if (effectTrackDepth <= maxMarkerBits) {
// 初始依赖追踪标记
initDepMarkers(this)
} else {
// 清除依赖追踪标记
cleanupEffect(this)
}
// 返回副作用函数执行结果
return this.fn()
} finally {
// 如果 effect调用栈的深度 没有超过阈值
if (effectTrackDepth <= maxMarkerBits) {
// 确定最终的依赖追踪标记
finalizeDepMarkers(this)
}
// 执行完毕会将 effectTrackDepth 减 1
trackOpBit = 1 << --effectTrackDepth
// 执行完毕,将当前活动的 ReactiveEffect 对象设置为 “父级作用域”
activeEffect = this.parent
// 将 shouldTrack 设置为上一个值
shouldTrack = lastShouldTrack
// 将父级作用域设置为 undefined
this.parent = undefined
// 延时停止,这个标志是在 stop 方法中设置的
if (this.deferStop) {
this.stop()
}
}
}
stop() {
// stopped while running itself - defer the cleanup
// 如果当前 活动的 ReactiveEffect 对象是 “自己”
// 延迟停止,需要执行完当前的副作用函数之后再停止
if (activeEffect === this) {
// 在 run 方法中会判断 deferStop 的值,如果为 true,就会执行 stop 方法
this.deferStop = true
} else if (this.active) {// 如果当前 ReactiveEffect 对象处于活动状态
cleanupEffect(this) // 清除所有的依赖追踪标记
if (this.onStop) {
this.onStop()
}
this.active = false // 将 active 设置为 false
}
}
}
- run方法的作用就是执行副作用函数,并且在执行副作用函数的过程中,会收集依赖;
- stop方法的作用就是停止当前的ReactiveEffect对象,停止之后,就不会再收集依赖了;
- activeEffect和this并不是每次都相等的,因为activeEffect会跟着调用栈的深度而变化,而this则是固定的;
三、依赖收集相关
1、如何触发依赖收集
在副作用函数中, obj.count
就会触发依赖收集
const runner = effect(() => {
console.log(obj.count)
})
触发的入口在get拦截器里面
function createGetter(isReadonly = false, shallow = false) {
// 闭包返回 get 拦截器方法
return function get(target: Target, key: string | symbol, receiver: object) {
// ...
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// ...
}
2、track源码
const targetMap = new WeakMap();
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (shouldTrack && activeEffect) { // 是否应该依赖收集 & 当前的new ReactiveEffect()即指向的就是当前正在执行的副作用函数
// 如果 targetMap 中没有 target,就会创建一个 Map
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())) // createDep 生成dep = { w:0, n: 0}
}
const eventInfo = __DEV__
? { effect: activeEffect, target, type, key }
: undefined
trackEffects(dep, eventInfo)
}
}
shouldTrack在上面也讲过,它的作用就是控制是否收集依赖;
activeEffect就是我们刚刚讲的ReactiveEffect对象,它指向的就是当前正在执行的副作用函数;
track方法的作用就是收集依赖,它的实现非常简单,就是在targetMap中记录下target和key;
targetMap是一个WeakMap,它的键是target,值是一个Map,这个Map的键是key,值是一个Set;
targetMap的结构伪代码如下:
targetMap = {
target: {
key: dep
},
// 比如:
obj: {
count: {
w: 0,
n: 0
}
}
}
以上是最原始的depMap
dev环境为增加响应式调试会增加eventInfo
const eventInfo = __DEV__
? { effect: activeEffect, target, type, key }
: undefined
eventInfo结构如下:
trackEffects(dep, eventInfo)
如果 dep 中没有当前的 ReactiveEffect 对象,就会添加进去, 作用就把对象的属性操作与副作用函数建立关联,接下来看trackEffects
3、trackEffects(dep, eventInfo)源码解读
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
let shouldTrack = false
if (effectTrackDepth <= maxMarkerBits) {
if (!newTracked(dep)) {
// 执行之前 dep = Set(0) {w: 0, n: 0}
// 执行之后 dep = Set(0) {w: 0, n: 2}
dep.n |= trackOpBit // set newly tracked
shouldTrack = !wasTracked(dep)
}
} else {
// Full cleanup mode.
shouldTrack = !dep.has(activeEffect!)
}
if (shouldTrack) {
// 将activeEffect添加到dep
dep.add(activeEffect!)
activeEffect!.deps.push(dep)
if (__DEV__ && activeEffect!.onTrack) { // onTrack逻辑
activeEffect!.onTrack(
extend(
{
effect: activeEffect!
},
debuggerEventExtraInfo!
)
)
}
}
}
dep.add(activeEffect!)
如果 dep 中没有当前的 ReactiveEffect 对象,就会添加进去
最终生成的depTarget结构如下:
四、触发依赖
比如例子中代码obj.count++
就会触发set拦截,触发依赖更新
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
//...
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) // 触发ADD依赖更新
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue) //触发SET依赖更新
}
}
//...
}
1、trigger依赖更新
// 路径:packages/reactivity/src/effect.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) // 获取depsMap, targetMap是在track中创建的依赖
if (!depsMap) {
// never been tracked
return
}
let deps: (Dep | undefined)[] = []
if (type === TriggerOpTypes.CLEAR) {
// 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)) {
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
}
}
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))
}
}
}
const depsMap = targetMap.get(target)
获取 targetMap 中的 depsMap targetMap结构如下:
执行以上语句之后的depsMap结构如下:
将 depsMap 中 key 对应的 ReactiveEffect 对象添加到 deps 中deps.push(depsMap.get(key))
之后的deps结构如下:
triggerEffects(deps[0], eventInfo)
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])
}
}
}
trigger函数的作用就是触发依赖,当我们修改数据的时候,就会触发依赖,然后执行依赖中的副作用函数。
在这里的实现其实并没有执行,主要是收集一些需要执行的副作用函数,然后在丢给triggerEffects函数去执行,接下来看看triggerEffects函数。
2、triggerEffects(deps[0], eventInfo)
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)
}
}
}
主要步骤
const effects = isArray(dep) ? dep : [...dep]
获取effects
triggerEffect(effect, debuggerEventExtraInfo)
执行effect,接下来看看源码
3、triggerEffect(effect, debuggerEventExtraInfo)
function triggerEffect(
effect: ReactiveEffect,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
if (effect !== activeEffect || effect.allowRecurse) {
// 如果 effect.onTrigger 存在,就会执行,只有开发模式下才会执行
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
}
// 如果 effect 是一个调度器,就会执行 scheduler
if (effect.scheduler) {
effect.scheduler()
} else {
// 其它情况执行 effect.run()
effect.run()
}
}
}
effect.run()就是执行副作用函数
以上就是源码分析Vue3响应式核心之effect的详细内容,更多关于Vue3 effect的资料请关注编程网其它相关文章!