之前我们了解过ReactiveEffect的核心就是run方法,现在我们主要了解他整体的构建思想
在track函数中 1.dep.add(activeEffect!)
2.activeEffect!.deps.push(dep)
不仅activeEffect记录到dep中,并且将dep放入activeEffect中的deps进行反向记录,那么为什么进行这一步操作呢----(主要目的就是为了让每一次更新后的依赖不会出错)
比如 watchEffect( console.log(msg.flag?'hello':msg.name)) 当我们刚开始进行依赖收集的时候,这个effect时加入msg.name的dep中,但是当msg.flag为true的时候,表达式返回hello,和msg.name无关了,当msg.name触发trigger的时候,还会触发当前这个effect嘛????-------- 不会,因为Vue会根据deps进行处理,每当触发当前effect的时候,Vue会清空deps,然后也会根据deps中的dep删除effect (详细可以看cleanupEffect源码)
function cleanupEffect(effect: ReactiveEffect) {
// 找到所有依赖这个 effect 的响应式对象
// 从这些响应式对象里面把 effect 给删除掉
const { deps } = effect
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
deps.length = 0
}
}
export class ReactiveEffect<T = any> {
active = true
//dep 数组,在响应式对象收集依赖时也会将对应的依赖项添加到这个数组中
deps: Dep[] = []
parent: ReactiveEffect | undefined = undefined
/**
* Can be attached after creation
* @internal
*/
computed?: ComputedRefImpl<T>
/**
* @internal
*/
allowRecurse?: boolean
/**
* @internal
*/
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)
}
run() {
// 当active=false,执行函数但不收集依赖
if (!this.active) {
return this.fn()
}
let parent: ReactiveEffect | undefined = activeEffect
let lastShouldTrack = shouldTrack
//循环遍历parent的父级元素
//直到找到了与当前实例this相同的父级元素,或者直到parent为null为止
//避免在组件的父子关系中出现循环引用的情况
while (parent) {
if (parent === this) {
return
}
parent = parent.parent
}
try {
//将上一个activeEffect先保存起来
this.parent = activeEffect
// 将自己设置为当前活跃的 ReactiveEffect
activeEffect = this
shouldTrack = true
//标记effect层级,执行一层effect,++effectTrackDepth
trackOpBit = 1 << ++effectTrackDepth
// 超过 maxMarkerBits 则 trackOpBit 的计算会超过最大整形的位数,降级为 cleanupEffect
if (effectTrackDepth <= maxMarkerBits) {
//给收集到的dpes数组打标记
initDepMarkers(this)
} else {
//重置deps数组依赖,需要重新收集
cleanupEffect(this)
}
// 执行fn
return this.fn()
} finally {
if (effectTrackDepth <= maxMarkerBits) {
// 执行完effect,看一下需要删除那些依赖添加哪些依赖
finalizeDepMarkers(this)
}
//标记effect层级,执行完--effectTrackDepth
trackOpBit = 1 << --effectTrackDepth
activeEffect = this.parent
shouldTrack = lastShouldTrack
this.parent = undefined
if (this.deferStop) {
this.stop()
}
}
}
stop() {
// stopped while running itself - defer the cleanup
if (activeEffect === this) {
this.deferStop = true
} else if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
}
上面只是3.0之前的想法,每当effect
再次执行的时候,都要先将上一次收集过的清空掉,重新进行收集,这么做的目的其实是为了避免上一次收集到的依赖,本次不需要去收集的情况所导致的依赖收集错误
但是大部分场景中依赖的变动其实是相对较小的,并不需要如此大刀阔斧的进行全部清空,再次收集。
因此Vue进行了优化处理,通过位运算和给dep打标记的方式进行了优化
Effect我们需要做几个事情 (代码过于零散--建议直接从源码 track,reactiveEffect慢慢入手,或者 阅读 Vue3.2中reactivity的优化 - 掘金 (juejin.cn))
- 在effect执行前,先将effectTrackDepth++
- 将原本收集到的dep打上自己的标记,作为旧标记
- 执行期间通过track给dep.n打上新标记
- 执行结束开始对比dep.w 和 dep.n,整理依赖
- effectTrackDepth--
// dep 构造
export const createDep = (effects?: ReactiveEffect[]): Dep => {
const dep = new Set<ReactiveEffect>(effects) as Dep
dep.w = 0 //旧标记
dep.n = 0 //新标记
return dep
}
// effect的追踪深度(effect层级)
let effectTrackDepth = 0
// 位标记
export let trackOpBit = 1
// init 打上旧标记
export const initDepMarkers = ({ deps }: ReactiveEffect) => {
if (deps.length) { //给deps打标记
for (let i = 0; i < deps.length; i++) {
deps[i].w |= trackOpBit // set was tracked
}
}
}
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
let shouldTrack = false
if (effectTrackDepth <= maxMarkerBits) {
if (!newTracked(dep)) {
//dep.n 打上标记位
dep.n |= trackOpBit // set newly tracked
//检查dep.w旧标记有没有打过标记并赋值给shouldTrack
shouldTrack = !wasTracked(dep)
}
} else {
// Full cleanup mode.
// 全面清理
shouldTrack = !dep.has(activeEffect!)
}
//TODO优化点(通过打标记)
// 1.wasTracked(dep)--如果dep.w打过标记说明已经存过,没有则说明没存过
// 2.通过dep.has(activeEffect)---观察依赖有无存在当前activeEffect中
if (shouldTrack) {
dep.add(activeEffect!)
activeEffect!.deps.push(dep)
if (__DEV__ && activeEffect!.onTrack) {
activeEffect!.onTrack(
extend(
{
effect: activeEffect!
},
debuggerEventExtraInfo!
)
)
}
}
}
// 对比cleanupEffect进行了一定程度的优化
// 大部分场景对于effect的依赖改动变化很小,直接使用cleanupEffect等于清空重新进行依赖收集
// 通过提前标记旧的依赖最后通过对比明显效果好很多
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中删除effect
dep.delete(effect)
} else { //保存dep
deps[ptr++] = dep
}
// clear bits
// 清除标记位--重置
dep.w &= ~trackOpBit
dep.n &= ~trackOpBit
}
deps.length = ptr
}
}
// reactiveEffect的run方法
run() {
// 当active=false,执行函数但不收集依赖
if (!this.active) {
return this.fn()
}
let parent: ReactiveEffect | undefined = activeEffect
let lastShouldTrack = shouldTrack
//循环遍历parent的父级元素
//直到找到了与当前实例this相同的父级元素,或者直到parent为null为止
//避免在组件的父子关系中出现循环引用的情况
while (parent) {
if (parent === this) {
return
}
parent = parent.parent
}
try {
//将上一个activeEffect先保存起来
this.parent = activeEffect
// 将自己设置为当前活跃的 ReactiveEffect
activeEffect = this
shouldTrack = true
//标记effect层级,执行一层effect,++effectTrackDepth
trackOpBit = 1 << ++effectTrackDepth
// 超过 maxMarkerBits 则 trackOpBit 的计算会超过最大整形的位数,降级为 cleanupEffect
if (effectTrackDepth <= maxMarkerBits) {
//给收集到的dpes数组打标记
initDepMarkers(this)
} else {
//重置deps数组依赖,需要重新收集
cleanupEffect(this)
}
// 执行fn
return this.fn()
} finally {
if (effectTrackDepth <= maxMarkerBits) {
// 执行完effect,看一下需要删除那些依赖添加哪些依赖
finalizeDepMarkers(this)
}
//标记effect层级,执行完--effectTrackDepth
trackOpBit = 1 << --effectTrackDepth
activeEffect = this.parent
shouldTrack = lastShouldTrack
this.parent = undefined
if (this.deferStop) {
this.stop()
}
}
}