You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
export functionreactive<Textendsobject>(target: T): UnwrapNestedRefs<T>exportfunctionreactive(target: object){// if trying to observe a readonly proxy, return the readonly version.if(target&&(targetasTarget)[ReactiveFlags.IS_READONLY]){returntarget}returncreateReactiveObject(target,false,mutableHandlers,mutableCollectionHandlers,reactiveMap)}
functioncreateGetter(isReadonly=false,shallow=false){returnfunctionget(target: Target,key: string|symbol,receiver: object)=>{constres=Reflect.get(target,key,receiver)if(!isReadonly){track(target,TrackOpTypes.GET,key)}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.returnisReadonly ? readonly(res) : reactive(res)}}}
functioncreateSetter(shallow=false){returnfunctionset(target: object,key: string|symbol,value: unknown,receiver: object): boolean{consthadKey=isArray(target)&&isIntegerKey(key)
? Number(key)<target.length
: hasOwn(target,key)constresult=Reflect.set(target,key,value,receiver)// don't trigger if target is something up in the prototype chain of originalif(target===toRaw(receiver)){if(!hadKey){trigger(target,TriggerOpTypes.ADD,key,value)}elseif(hasChanged(value,oldValue)){trigger(target,TriggerOpTypes.SET,key,value,oldValue)}}returnresult}}
switch(type){caseTriggerOpTypes.ADD:
if(!isArray(target)){add(depsMap.get(ITERATE_KEY))if(isMap(target)){add(depsMap.get(MAP_KEY_ITERATE_KEY))}}elseif(isIntegerKey(key)){// new index added to array -> length changesadd(depsMap.get('length'))}
it('should not be triggered by mutating a property, which is used in an inactive branch',()=>{letdummyconstobj=reactive({prop: 'value',run: true})constconditionalSpy=jest.fn(()=>{dummy=obj.run ? obj.prop : 'other'})effect(conditionalSpy)expect(dummy).toBe('value')expect(conditionalSpy).toHaveBeenCalledTimes(1)obj.run=falseexpect(dummy).toBe('other')expect(conditionalSpy).toHaveBeenCalledTimes(2)obj.prop='value2'expect(dummy).toBe('other')expect(conditionalSpy).toHaveBeenCalledTimes(2)})
看到这个测试的时候,明白了为什么每次都需要 cleanup 了,这里的重点是在 conditionalSpy 内,这里 track 的前提是需要触发 get 之类的操作,而如果说在 effect 的 fn 里面因为有条件逻辑存在的话,就是会有触发不到某个 get 的时候。
exportclassReactiveEffect<T=any>{
active =truedeps: Dep[]=[]// can be attached after creationcomputed?: booleanallowRecurse?: booleanonStop?: ()=>void// dev onlyonTrack?: (event: DebuggerEvent)=>void// dev onlyonTrigger?: (event: DebuggerEvent)=>voidconstructor(publicfn: ()=>T,publicscheduler: EffectScheduler|null=null,scope?: EffectScope|null){recordEffectScope(this,scope)}run(){}stop(){}}
/** * The bitwise track markers support at most 30 levels op recursion. * This value is chosen to enable modern JS engines to use a SMI on all platforms. * When recursion depth is greater, fall back to using a full cleanup. */constmaxMarkerBits=30
exportconstinitDepMarkers=({ deps }: ReactiveEffect)=>{if(deps.length){for(leti=0;i<deps.length;i++){deps[i].w|=trackOpBit// set was tracked}}}
这里也是用了二进制的处理方式来做的标记逻辑。
看看这里的 dep.w 都用在了哪里
以上逻辑的关键点是优化了之前每次都调用 cleanup 的点
exportfunctiontrackEffects(dep: Dep,debuggerEventExtraInfo?: DebuggerEventExtraInfo){letshouldTrack=falseif(effectTrackDepth<=maxMarkerBits){if(!newTracked(dep)){dep.n|=trackOpBit// set newly trackedshouldTrack=!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(Object.assign({effect: activeEffect!},debuggerEventExtraInfo))}}}
it('stop',()=>{letdummyconstobj=reactive({prop: 1})construnner=effect(()=>{dummy=obj.prop})obj.prop=2expect(dummy).toBe(2)stop(runner)obj.prop=3expect(dummy).toBe(2)// stopped effect should still be manually callablerunner()expect(dummy).toBe(3)})
exportfunctioncomputed<T>(getterOrOptions: ComputedGetter<T>|WritableComputedOptions<T>,debugOptions?: DebuggerOptions){
let getter: ComputedGetter<T>letsetter: ComputedSetter<T>// 初始化的逻辑if(isFunction(getterOrOptions)){getter=getterOrOptionssetter=__DEV__
? ()=>{console.warn('Write operation failed: computed value is readonly')}
: NOOP}else{getter=getterOrOptions.getsetter=getterOrOptions.set}constcRef=newComputedRefImpl(getter,setter,isFunction(getterOrOptions)||!getterOrOptions.set)returncRefasany}
一开始先找到 getter 和 setter
接着看 ComputedRefImpl 的实现
classComputedRefImpl<T>{publicdep?: Dep=undefinedprivate_value!: Tprivate_dirty=truepublicreadonlyeffect: ReactiveEffect<T>publicreadonly__v_isRef=truepublicreadonly[ReactiveFlags.IS_READONLY]: booleanconstructor(getter: ComputedGetter<T>,privatereadonly_setter: ComputedSetter<T>,isReadonly: boolean){this.effect=newReactiveEffect(getter,()=>{if(!this._dirty){this._dirty=truetriggerRefValue(this)}})this[ReactiveFlags.IS_READONLY]=isReadonly}
get value() {// the computed ref may get wrapped by other proxies e.g. readonly() #3376constself=toRaw(this)trackRefValue(self)if(self._dirty){self._dirty=falseself._value=self.effect.run()!}
return self._value
}setvalue(newValue: T){this._setter(newValue)}}/
getvalue(){// the computed ref may get wrapped by other proxies e.g. readonly() #3376constself=toRaw(this)trackRefValue(self)if(self._dirty){self._dirty=falseself._value=self.effect.run()!}returnself._value}
这里的重点是搞清楚 _dirty 的逻辑
选择一个单元测试,使用断点
it('should compute lazily',()=>{constvalue=reactive<{foo?: number}>({})constgetter=jest.fn(()=>value.foo)constcValue=computed(getter)// lazyexpect(getter).not.toHaveBeenCalled()expect(cValue.value).toBe(undefined)expect(getter).toHaveBeenCalledTimes(1)// should not compute againcValue.valueexpect(getter).toHaveBeenCalledTimes(1)// should not compute until neededvalue.foo=1expect(getter).toHaveBeenCalledTimes(1)
test('should only trigger once on multiple mutations',async()=>{constsrc=ref(0)constc=deferredComputed(()=>src.value)constspy=jest.fn()effect(()=>{spy(c.value)})expect(spy).toHaveBeenCalledTimes(1)src.value=1src.value=2src.value=3// not called yetexpect(spy).toHaveBeenCalledTimes(1)awaittick// should only trigger onceexpect(spy).toHaveBeenCalledTimes(2)expect(spy).toHaveBeenCalledWith(c.value)})
exportfunctiondeferredComputed<T>(getter: ()=>T): ComputedRef<T>{returnnewDeferredComputedRefImpl(getter)as any
}
classDeferredComputedRefImpl<T>{publicdep?: Dep=undefinedprivate_value!: Tprivate_dirty=trueconstructor(getter: ComputedGetter<T>){letcompareTarget: anylethasCompareTarget=falseletscheduled=falsethis.effect=newReactiveEffect(getter,(computedTrigger?: boolean)=>{if(this.dep){if(computedTrigger){compareTarget=this._valuehasCompareTarget=true}elseif(!scheduled){const valueToCompare =hasCompareTarget ? compareTarget : this._valuescheduled=truehasCompareTarget=falsescheduler(()=>{if(this.effect.active&&this._get()!==valueToCompare){triggerRefValue(this)}scheduled=false})}// chained upstream computeds are notified synchronously to ensure// value invalidation in case of sync access; normal effects are// deferred to be triggered in scheduler.for(consteofthis.dep){if(e.computed){e.scheduler!(true/* computedTrigger */)}}}this._dirty=true})this.effect.computed=true}private_get(){if(this._dirty){this._dirty=falsereturn(this._value=this.effect.run()!)}
return this._value
}getvalue(){trackRefValue(this)// the computed ref may get wrapped by other proxies e.g. readonly() #3376returntoRaw(this)._get()}}
这里的几个属性都是和 computed 是一样的,比如有 dep 、 dirty、和 value
看看区别是什么
get 函数的实现和 computed 是差不多的,但是他没有 set 。
getvalue(){trackRefValue(this)// the computed ref may get wrapped by other proxies e.g. readonly() #3376returntoRaw(this)._get()}
constructor(getter: ComputedGetter<T>){letcompareTarget: anylethasCompareTarget=falseletscheduled=falsethis.effect=newReactiveEffect(getter,(computedTrigger?: boolean)=>{if(this.dep){if(computedTrigger){compareTarget=this._valuehasCompareTarget=true}elseif(!scheduled){const valueToCompare =hasCompareTarget ? compareTarget : this._valuescheduled=truehasCompareTarget=falsescheduler(()=>{if(this.effect.active&&this._get()!==valueToCompare){triggerRefValue(this)}schedulerscheduled=false})}// chained upstream computeds are notified synchronously to ensure// value invalidation in case of sync access; normal effects are// deferred to be triggered in scheduler.for(consteofthis.dep){if(e.computed){e.scheduler!(true/* computedTrigger */)}}}this._dirty=true})this.effect.computed=true}
run<T>(fn: ()=>T): T|undefined{if(this.active){try{this.on()returnfn()}finally{this.off()}}elseif(__DEV__){warn(`cannot run an inactive effect scope.`)}}
exportfunctiononScopeDispose(fn: ()=>void){if(activeEffectScope){activeEffectScope.cleanups.push(fn)}elseif(__DEV__){warn(`onDispose() is called when there is no active effect scope `+` to be associated with.`)}}
看的见的思考
reactive
从这个单元测试的 demo 开始
先从 createReactiveObject 开始
这里别的什么都不需要管,先看看他做了什么事
不需要关心 mutableHandlers、mutableCollectionHandlers、reactiveMap 都做了什么
在 createReactiveObject 里面最核心的逻辑是
最核心的逻辑其实就是用 Proxy 给包裹一下,然后这里是需要基于一个 targetType 的类型去选择用不同的 handlers 的
那我们看看 targetType 都有哪几种?
看 getTargetType
无效的和正常了 type 做了区分
无效的情况是:
有 ReactiveFlags.SKIP 字段 或者 对象是不可以扩展的
那在什么情况下需要对象用到扩展呢?
剩下的点都在 targetTypeMap 里面声明好了
所以总结一下就是:
TargetType.COMMON:
Array
Object
TargetType.COLLECTION
Map
Set
WeakMap
WeakSet
TargetType.INVALID
好 ,类型知道了,那我们的参数是一个对象 {foo:xxx} 所以看看 handlers 应该是 baseHandlers
那么 handlers 就是
TargetType.COMMON → baseHandlers → mutableHandlers
TargetType.COLLECTION → collectionHandlers → mutableCollectionHandlers
mutableHandlers
先看这个方法
这里把所有的处理都封装到了具体的函数内了,这次在看要比之前好很多
接着看看 get
get
get 就是调用了 createGetter
这里是使用了闭包的概念,让调用者可以少写几个参数,而且从概念上也做了分离,在create 的时候就可以标注是不是 readonly 的 或者是 shallow 的
这里的 get 调用的时机是在触发 proxy.xxx 的 get 操作的时候
而在调用 get 的时候是需要做依赖收集的,而依赖收集的动作就是在 track 里面做的!
而最终的结果就是返回 Reflect.get 的值就可以了
这里有个点,就是如何处理嵌套的 Object, 就是递归的调用 reactive 即可
track 依赖收集
重点:那是如何做依赖收集的呢???
这里的重点就是如何存储 dep
这里有两层关系,
target → depsMap
key → dep
例如:
那 target 就是 user
target 对应的就是 depsMap
age 对应一个 dep
那 activeEffect 是什么呢?其实就是依赖(通过 effect 给到的 function)
而把 activeEffect 添加到dep里面的操作就是依赖收集
到这里依赖收集的动作就已经都搞定了
需要看看 activeEffect 是从哪里过来的
这里的 activeEffect 是在 createReactiveEffect 赋值的
所以这个 activeEffect 确实是调用 effect 时候的依赖函数
在来分析分析这几个参数
target 就是对应的对象
type 的作用是后续给 debug 的时候调用的,方便用户知道当前是什么类型
key 是什么?
key 按照以前的理解是 target 对应的 key ,但是下面的 trigger 的时候发现这个key 可能并不只有一种情况,那么我们看看key 都有什么情况把
啊哦,其实大多数情况下都是有key 的,但是有一些特殊的操作是没有key 的,那怎么办? 自己搞一个被,所以这个就是 ITERATE_KEY 的作用
比如在 ownkeys 的时候,在 size 的时候
Set
这里的核心逻辑是 trigger 也就是触发依赖
但是触发依赖的时候是分情况的,一个是 ADD 一个是 SET
TODO 如何区分触发依赖的时候是 ADD 还是 SET?
先看trigger 的逻辑实现
先看 Add 的逻辑
这里是基于不同的类型来从获取 depsMap 里面获取值
那看看 add 干了啥?
啊哦, 这里是用 effects 来把 effect 给添加进来的,
那为什么要在这里添加呢? 不是应该在收集依赖的时候添加吗? 看看收集依赖做了啥?
track 的时候确实是收集了 effect 里面的依赖函数,没有问题
那看看 effects 后续是用在哪里了吧
在最后一行调用了 effects 执行 run 函数,那这里应该是在 trigger 的时候把之前所有的依赖又重新收集了一遍,那为什么又重新收集了一遍呢?方便后续的统一处理,因为需要在多个地方把所有的 effect 都收集起来
这里的 ITERATE_KEY 是什么? 他是在哪里赋值的?是在什么时候存给 depsMap 的呢?
只是一个唯一标识
那看看在哪里存的 ,存的又是什么, 啊哦
在调用 track 的时候会吧这个值给过去,比如:在 ownKeys 的时候 ,那这里需要看看 track 的几个关键的参数
那么如果我们精简一下 set 做的事的话,那么就是:
收集所有的 effect (依赖)
使用的是 add 函数来添加
调用所有的 effect
调用的 run 函数来处理
那么继续去看看 run 函数都做了什么吧
也是非常简单,就是调用一下收集过来的 effect (依赖)
而这里有个特殊处理就是 scheduler 的逻辑实现,这里其实就是调用 scheduler ,让用户自己处理调用的时机(这里先略过)
effect
fn就是传入的 function ,这里的核心就是创建一个 effect 对象,然后返回即可
接下来看 createReactiveEffect
effect 本身就是一个函数,然后给了它很多的属性
这里是使用
effectStack
来存储所有的effect
,以及使用activeEffect
这个全局的变量来保存当前的effect
这里的activeEffect
就和track
依赖收集关联起来了, 因为 effect 这个函数就是响应式对象的依赖这里需要探索的是
enableTracking
resetTracking
cleanup
先看 enableTracking
咦,这里的 trackStack 又是一个栈,他是做什么的呢?
看看他都是在哪里调用了,trackStack 只是会影响 shouldTrack ,那重点是 shoudlTrack
最终的使用是会影响到 track 的逻辑
那为什么需要用 stack 来管理一个布尔变量呢?
这里的关键点是 pauseTracking 函数的调用,但是使用它的地方太多了
结合pauseTracking enableTracking resetTracking 来判断的话,应该是需要返回上一个 shouldTrack 的状态
那我们只分析 try 内部的代码的话
就是先允许 track 然后把 effect 记录到 effectStack 内,接着执行 fn()
而执行 fn 的时候会触发 track 的逻辑,所以正好把当前的 effect 给收集进去了
那我们在看看 finally 的逻辑
这里的 finally 逻辑是肯定会执行的
所以也就是在收集完依赖后,就需要把之前的 effect 给弹出去了,然后还 resetTracking,但是这里的 resetTracking 是回到上一个状态。 最后把 activeEffect 制成栈顶的值
但是这里是应对什么场景的呢???
可能是嵌套的。TODO
接着看看 cleanup 的逻辑,因为在跟断点的时候发现每次收集到的 dep 都是会被清理的。也就是执行一次,清理一次。
这里的 deps 是 effect 依赖对应的响应式对象的 dep,之前在 track 的时候 收集进来的。
这里的目的就是把响应式对象里面的依赖都清空。至于为什么,只能是找到对应的 demo 才明白。(应该是为了处理 边缘 case)
看单元测试:
这里本来以为赋值2个key 的话,只会触发一次,但是其实是修改一个key就会触发一次 fn的
如果是个嵌套的对象的话,这里的 counter.nested 的依赖会是 fn,而 counter.nested.num 的依赖也会是 fn
那这个调用的链路都会收集 fn
看到这个测试的时候,明白了为什么每次都需要 cleanup 了,这里的重点是在 conditionalSpy 内,这里 track 的前提是需要触发 get 之类的操作,而如果说在 effect 的 fn 里面因为有条件逻辑存在的话,就是会有触发不到某个 get 的时候。
比如 conditionalSpy ,在 obj.run 为 true 的时候,这里会触发 obj.run 也会触发 obj.prop。
而 obj.run 变成 false 的时候,这里只会触发 obj.run 。 而只触发 obj.urn 就会意味着 obj.prop 没有收集进来,那当我们去修改了 obj.prop 的值的时候,就不会在执行 effect fn 了。
那如果说我们把代码的执行 path 比作绘画的话,因为这个 path 会改变,所以我们就需要每次都 清空掉,然后重新绘制
阅读 3.2 版本
变化点在 effect.ts 里面
首先 effect 是用 class 来表示了
只有2个行为,run 和 stop 了
在看 effect 的逻辑
这里的 scope 是做什么用的
执行的时候就是用 _effect.run() 调用即可
这时候的 effect 函数会返回一个新的概念 ReactiveEffectRunner 类型。
那看看这个类型都是做了什么事把
这里的 runner 也就是 ReactiveEffectRunner 就是 _effect.run.bind 之后的这个函数
接着看看 effect 里面的 run 都是做了什么
这里和之前的版本的变化点是控制 track 的布尔值的逻辑变了。别的都是和以前一样的
那重点就看看新增加的逻辑是什么
这里的 trackOpBit 和 initDepMarkers 和 cleanupEffect 方法
对比之前的实现的话,每次都是必须会调用 cleanupEffect 来清理依赖的。而现在不是了
先看看 effectTrackDepth
对effectTrackDepth 的赋值是在 try 里面的时候 effectTrackDepth 会执行 ++effectTrackDepth,
而 effectTrackDepth 会影响到initDepMarkers和cleanupEffect 以及finalizeDepMarkers 的执行
这里还有一个关键的控制变量是maxMarkerBits
感觉是个控制值
这里的 SMI ,是什么?TODO
目前猜测是基于v8的某些点做的优化策略
目前是
那看看小于 maxMarkerBits 的时候的 initDepMarkers 吧
这里也是用了二进制的处理方式来做的标记逻辑。
看看这里的 dep.w 都用在了哪里
以上逻辑的关键点是优化了之前每次都调用 cleanup 的点
逻辑线索在 shouldTrack , 都是为了算出是不是需要 track,那么看看有没有 demo 可以验证这个猜测
在这个单测里面证明了上面的猜测,现在我们知道了解决的是什么问题(why)
接着看看是如何做到的 how
如果从优化角度自己思考的话,是希望
track 收集依赖只收集一次就好了
但是只收集一次的话,code path 变化了要如何解决?
好了,这里使用二进制的原因是因为会有多层级,而用二进制来表示每一个层级的标识
而对于一个 dep 来讲会有2个标识
n → 就是代表在当前的递归层级中是不是初始化过的
我们在简化一下,不考虑递归层级的问题
w→在当前的递归层级中是不是已经被 track 的
接着我们看看 dep.n 和 dep.w 都是分别在什么时候被赋值的
stop 的实现
看看stop 的功能是如何实现的
这里 stop 是停止侦听,而 runner 是重新run起来
看看stop 都做了什么事
这里的关键是调用了 cleanupEffect 这个函数,是会把当前的 effect 的 deps 都清空掉
那么也就是说响应式对象里面是没有依赖了,所以当触发 effect 的 trigger 的时候,是没有依赖可以执行的
而再次可以运行的逻辑其实就是重新调用一遍 effect.run
这里的 runner 就是指向的 effect.run 函数
所以后续的操作就是和初始化的逻辑一样了,需要重新的执行 fn ,然后再重新的收集依赖
lazy 的实现
lazy 是为了让用户自己选择调用的时机
如果 lazy 为true 的话,那么在执行 effect 的时候,是不会主动执行 run 的,(不会执行 run 就意味着不会执行用户给的 fn),然后执行 effect 之后是会返回 runner 的,这里用户可以自己选择在什么时候去执行
实现也比较简单
EffectScope 是什么
暂时看不出来是在哪里使用的
但是他的职责就是收集所有的 effect
计算属性的实现 computed
先从 test 入手
computed 的逻辑
一开始先找到 getter 和 setter
接着看 ComputedRefImpl 的实现
computed 的核心就是 effect ,而这里是使用了 effect 的 scheduler 的功能
后续执行 effect.run 的时候需要做一些额外的处理
看看 dirty 和 triggerRefValue 是做了什么
因为我们知道 computed 的一个核心点是可以有缓存的,就是没有变化的话,会返回之前的值
主要也是看看 computed 的这个核心功能是如何实现的
先看看 triggerRefValue 的实现
这里涉及到了 ref 的代码实现了, 哦 算了 , 计算属性先等等在看,先看 ref ,
这个函数在 ref 的时候都分析完了,可以去看看 ref 的分析
继续
计算属性的话,只有在执行 get 操作的时候才会触发后续的逻辑
构造器里面只是创建了 effect,但是并没有执行
接着就看看 get value 的逻辑吧
这里的重点是搞清楚 _dirty 的逻辑
选择一个单元测试,使用断点
首先是调用了 new ComputedRefImpl
然后再初始化的时候创建了 effect ,这里要注意的是
第二个参数是scheduler 的实现,当执行 effect.run 的时候会执行 fn,当触发了 trigger 逻辑的时候会执行 scheduler 内部的实现
顺序是:
执行 new ReactiveEffect
用户执行 get value 的操作
触发 get
执行 trackRefValue 收集依赖?这里是处理未知情况的逻辑
触发 effect.run()
执行用户给的 fn
触发收集依赖
用户修改了内部响应式对象的值
触发 trigger
ref 的实现
先从这里看起
用户实际使用的就是 RefImpl 的实例
而 refimpl 一共就2个行为, get value 和 set value
而基于我们之前看 reactive的经验,知道在 get 的时候是会 track 的,在 set 的时候会 trigger 的
而 ref 这里的实现也是一样的
先看 trackRefValue
这里的实现就简单了,因为一个 ref 只会对应一个值,所以实现的时候会创建一个 dep 赋值给 ref.dep 上
然后后面的执行和 reactive 一样了。都是执行 trackEffects来处理
这里有个问题,如果说传入的值是 object 或者是 array 的话,那怎么办?
答案就是会用 reactive 包裹一下,这个逻辑是在 构造器里面实现的
看 convert 的实现
啊哦,如果是个 object 的话,那么就用 reactive 包裹了
那继续去看 set逻辑
这里处理了2个点
这里也和之前是一样的,如果有 dep 的话,那么就 trigger 一下就可以了
DeferredComputed 的实现
先从单元测试看起
大概先猜测的话,这里的 deferredComputed 和 computed 大概差不多,一上来就会执行,但是不同的是后续在改变他的值的时候他不会立即执行了。而是等到 await tick 之后才会执行
那看看是如何实现的
程序的入口
这里的几个属性都是和 computed 是一样的,比如有 dep 、 dirty、和 value
看看区别是什么
get 函数的实现和 computed 是差不多的,但是他没有 set 。
同样都是使用 dirty 给锁上
那重点应该就是在 trigger 的实现里面
这里的 computedTrigger 参数是什么?
这里第二个参数是 scheduler ,我们看看当调用 scheduler 的时候给传入的什么参加就可以了
咦,看了看 effect 里面当调用 scheduler 的时候 并没有什么参数呀?
那执行看看
这个逻辑实在是太绕了。先放弃
这里有个有价值的是,进队列,在异步后执行的实现逻辑
这里有个缺点就是没有看看进来的 fn 是不是已经收集过的。
effectScope 的实现
当 reactivity 这个库单独拿出去使用的时候,就会出现一系列的问题。
比如创建出来的 effect 变多了,如何去统一的 stop 掉。这个 api 就是解决这个问题的
之前在 vue 中使用的话,所有的 effect 都是和组件绑定在一起的。所以当组件销毁的时候,他会自动的把组件内所有的 effect 都 stop 掉。
先看看他的几个功能点
收集所有的 effect
包含了 effect
watch
computed
watchEffect
可以统一的停止所有的 effect
也就是调用收集起来的 effect.stop
可以有多个 scope 联合起来使用
多个 scope 就会涉及到树结构
当 parent scope 清理的时候所有的 children 都会被清理掉
也可以设置一下 具体的那个 scope 可以不会被清理
从 api 上来看的话分为以下几个行为:
1. Basic Usage
Nested Scopes
Detached Nested Scopes
4.
onScopeDispose
好了,接下来就依次来看看是如何实现的
看看是如何收集所有的 effect 的
关键函数是 this.on 和 this.off
咦,这里只做到了把 effectScope 自己收集起来,那在什么时候收集的 run 里面的 effect 呢?
这个问题的答案是在这里 recordEffectScope
scope 就是 effectScope 了。而里面的逻辑也很简单 就是把 effect 给收集起来,而这个函数是在哪里调用的呢?
哈哈,是在 new ReactiveEffect 的时候,所以只要是 effect 创建了,那么就会收集到当前的 scope 里面了,这样就完成了收集 effect 的动作
这里有个发现是,在创建 effect 的时候,你可以指定具体的 scope 。不然的话 就是 activeEffectScope (当前的 scope 来收集 effect 了)
我们继续去看看 this.off 的实现
可以看到,这里是用 stack 来管理收集递归调用的,这里也和程序执行用 stack 来管理是一样的道理
stop 功能是如何实现的
如果只忽略其他功能的话,那么核心只是一行代码
把收集起来的所有的 effect 都执行 stop 就完事了
剩下的逻辑是处理其他功能的。我们接着依次去看看
scope 里面会嵌套 scope,当执行 stop 的时候,内部的 scope 里面所有的 effect 也会都 stop,这个是怎么实现的?
如果让我自己来实现的话,那么首先是当前的 scope 应该把内部的所有 scope 都存起来,然后再 stop 的时候在调用内部 scope.stop 逻辑
那么先看看如何存的把:
这里是用 scopes 字段来存储的
那看看在哪里 push 的把
是在构造器里面执行的 push ,只不过这里判断了 detached 变量,因为在功能的使用上,如果当然的 scope 的 detached 为true 的话,那么表示当前的 scope 是不可以被清理的
又因为 activeEffectScope 是在执行 this.on 的时候才会被赋值,现在还是之前的也就是上一个 scope 所以可以是当前的 scope 的 parent
然后使用 parent 把当前的 scope 给收集起来,这样就完成了收集的处理,
在去看看 stop 时候的处理
简单 ,和刚刚上面猜测的一样,调用 scope 的 stop 方法就可以了
Detached Nested Scopes 如果 scope 是独立的话,那么当 parent scope 调用 stop 的时候 不应该被清理
这里的实现也很简单了,想一想,如果是 Detached 的话,那么就不应该被清除,那么只需要不收集到 scopes 里面不就完事了吗。 所以在构造器里面的实现就是判断一下
detached 是 true 的话,那么你就别给我收集了
onScopeDispose
是类似于组件的 onUnMounted 的功能的也就是在 stop 执行完成后,被调用。让用户可以处理一些副作用(比如清空一些事件侦听)
这里要注意的是, 清理函数是可以有多个的,所以这里是用 数组来存 cleanups
onScopeDispose 的逻辑就是收集所有的处理函数
接着是在 stop 完成之后调用即可
可以看到,这里的 cleanups 的执行是在 当前 scope 所有的 effect都执行完 stop 之后调用的
知识点
可以利用 stack 来处理递归的每一个状态
使用 Stack 来处理递归嵌套
可以使用二进制的方式来管理多个状态
这里的限制是这个状态只能是个 boolean
问题
targetType 都有哪几种?✅
如何区分触发依赖的时候是 ADD 还是 SET?✅
为什么清空 cleanup ,清空 effect 里面的 deps ✅
执行的代码变了,所以需要全部重新执行
effectsStack 和 trackStack 是为了处理什么场景的?✅
嵌套的场景
一个 stack 需要对应一个 effect
多层级递归调用的 demo 是哪一个 ✅
v8里面的 SMI 是什么?
总结
track 都做了什么
收集依赖
trigger 都做了什
调用收集到的所有的依赖
function ownKeys(target: object): (string | symbol)[] {
track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
return Reflect.ownKeys(target)
}
The text was updated successfully, but these errors were encountered: