Vue3 computed和watch怎么实现
computed
computed和watch在面试中经常被问到他们的区别,那么我们就从源码的实现来看看他们的具体实现
//packages/reactivity/src/computed.ts exportfunctioncomputedT> ( getterOrOptions:ComputedGetterT> |WritableComputedOptionsT> , debugOptions?:DebuggerOptions, isSSR=false ){ letgetter:ComputedGetterT> letsetter:ComputedSetterT> constonlyGetter=isFunction(getterOrOptions) if(onlyGetter){ getter=getterOrOptions setter=__DEV__ ?()=> { console.warn('Writeoperationfailed:computedvalueisreadonly') } :NOOP } else{ getter=getterOrOptions.get setter=getterOrOptions.set } //newComputedRefImpl constcRef=newComputedRefImpl(getter,setter,onlyGetter||!setter,isSSR) if(__DEV__& & debugOptions& & !isSSR){ cRef.effect.onTrack=debugOptions.onTrack cRef.effect.onTrigger=debugOptions.onTrigger } //返回ComputedRefImpl实例 returncRefasany }
可以看到computed内部只是先处理getter和setter,然后new一个ComputedRefImpl返回,如果你知道ref API的实现,可以发现他们的实现有很多相同之处
ComputedRefImpl
//packages/reactivity/src/computed.ts exportclassComputedRefImplT> { publicdep?:Dep=undefined//存储effect的集合 private_value!:T publicreadonlyeffect:ReactiveEffectT> publicreadonly__v_isRef=true publicreadonly[ReactiveFlags.IS_READONLY]:boolean=false public_dirty=true//是否需要重新更新value public_cacheable:boolean constructor( getter:ComputedGetterT> , privatereadonly_setter:ComputedSetterT> , isReadonly:boolean, isSSR:boolean ){ //创建effect this.effect=newReactiveEffect(getter,()=> { //调度器执行重新赋值_dirty为true if(!this._dirty){ this._dirty=true //触发effect triggerRefValue(this) } } ) //用于区分effect是否是computed this.effect.computed=this this.effect.active=this._cacheable=!isSSR this[ReactiveFlags.IS_READONLY]=isReadonly } getvalue(){ //thecomputedrefmaygetwrappedbyotherproxiese.g.readonly()#3376 //computedref可能被其他代理包装,例如readonly()#3376 //通过toRaw()获取原始值 constself=toRaw(this) //收集effect trackRefValue(self) //如果是脏的,重新执行effect.run(),并且将_dirty设置为false if(self._dirty||!self._cacheable){ self._dirty=false //run()方法会执行getter方法值会被缓存到self._value self._value=self.effect.run()! } returnself._value } setvalue(newValue:T){ this._setter(newValue) } }
可以看到ComputedRefImplget的get实现基本和ref的get相同(不熟悉ref实现的请看上一章),唯一的区别就是_dirty值的判断,这也是我们常说的computed会缓存value,那么computed是如何知道value需要更新呢?
可以看到在computed构造函数中,会建立一个getter与其内部响应式数据的关系,这跟我们组件更新函数跟响应式数据建立关系是一样的,所以与getter相关的响应式数据发生修改的时候,就会触发getter effect 对应的scheduler,这里会将_dirty设置为true并去执行收集到的effect(这里通常是执行get里收集到的函数更新的effect),然后就会去执行函数更新函数,里面会再次触发computed的get,此时dirty已经被置为true,就会重新执行getter获取新的值返回,并将该值缓存到_vlaue。
小结:
所以computed是有两层的响应式处理的,一层是computed.value和函数的effect之间的关系(与ref的实现相似),一层是computed的getter和响应式数据的关系。
注意:如果你足够细心就会发现函数更新函数的effect触发和computed getter的effect的触发之间可能存在顺序的问题。假如有一个响应式数据a不仅存在于getter中,还在函数render中早于getter被访问,此时a对应的dep中更新函数的effect就会早于getter的effect被收集,如果此时a被改变,就会先执行更新函数的effect,那么此时render函数访问到computed.value的时候就会发现_dirty依然是false,因为getter的effect还没有被执行,那么此时依然会是旧值。vue3中对此的处理是执行effects的时候会优先执行computed对应的effect(此前章节也有提到):
//packages/reactivity/src/effect.ts exportfunctiontriggerEffects( dep:Dep|ReactiveEffect[], debuggerEventExtraInfo?:DebuggerEventExtraInfo ){ //spreadintoarrayforstabilization consteffects=isArray(dep)?dep:[...dep] //computed的effect会先执行 //防止render获取computed值得时候_dirty还没有置为true for(consteffectofeffects){ if(effect.computed){ triggerEffect(effect,debuggerEventExtraInfo) } } for(consteffectofeffects){ if(!effect.computed){ triggerEffect(effect,debuggerEventExtraInfo) } } }
watch
watch相对于computed要更简单一些,因为他只用建立getter与响应式数据之间的关系,在响应式数据变化时调用用户传过来的回调并将新旧值传入即可
//packages/runtime-core/src/apiWatch.ts exportfunctionwatchT=any,ImmediateextendsReadonlyboolean> =false> ( source:T|WatchSourceT> , cb:any, options?:WatchOptionsImmediate> ):WatchStopHandle{ if(__DEV__& & !isFunction(cb)){ warn(...) } //watch具体实现 returndoWatch(sourceasany,cb,options) }
functiondoWatch( source:WatchSource|WatchSource[]|WatchEffect|object, cb:WatchCallback|null, { immediate,deep,flush,onTrack,onTrigger} :WatchOptions=EMPTY_OBJ ):WatchStopHandle{ if(__DEV__& & !cb){ ... } constwarnInvalidSource=(s:unknown)=> { warn(...) } constinstance= getCurrentScope()===currentInstance?.scope?currentInstance:null //constinstance=currentInstance letgetter:()=> any letforceTrigger=false letisMultiSource=false //根据不同source创建不同的getter函数 //getter函数与computed的getter函数作用类似 if(isRef(source)){ getter=()=> source.value forceTrigger=isShallow(source) } elseif(isReactive(source)){ //source是reactive对象时自动开启deep=true getter=()=> source deep=true } elseif(isArray(source)){ isMultiSource=true forceTrigger=source.some(s=> isReactive(s)||isShallow(s)) getter=()=> source.map(s=> { if(isRef(s)){ returns.value } elseif(isReactive(s)){ returntraverse(s) } elseif(isFunction(s)){ returncallWithErrorHandling(s,instance,ErrorCodes.WATCH_GETTER) } else{ __DEV__& & warnInvalidSource(s) } } ) } elseif(isFunction(source)){ if(cb){ //getterwithcb getter=()=> callWithErrorHandling(source,instance,ErrorCodes.WATCH_GETTER) } else{ //nocb-> simpleeffect getter=()=> { if(instance& & instance.isUnmounted){ return } if(cleanup){ cleanup() } returncallWithAsyncErrorHandling( source, instance, ErrorCodes.WATCH_CALLBACK, [onCleanup] ) } } } else{ getter=NOOP __DEV__& & warnInvalidSource(source) } //2.xarraymutationwatchcompat //兼容vue2 if(__COMPAT__& & cb& & !deep){ constbaseGetter=getter getter=()=> { constval=baseGetter() if( isArray(val)& & checkCompatEnabled(DeprecationTypes.WATCH_ARRAY,instance) ){ traverse(val) } returnval } } //深度监听 if(cb& & deep){ constbaseGetter=getter //traverse会递归遍历对象的所有属性以达到深度监听的目的 getter=()=> traverse(baseGetter()) } letcleanup:()=> void //watch回调的第三个参数可以用此注册一个cleanup函数会在下一次watchcb调用前执行 //常用于竞态问题的处理 letonCleanup:OnCleanup=(fn:()=> void)=> { cleanup=effect.onStop=()=> { callWithErrorHandling(fn,instance,ErrorCodes.WATCH_CLEANUP) } } //inSSRthereisnoneedtosetupanactualeffect,anditshouldbenoop //unlessit'seagerorsyncflush letssrCleanup:(()=> void)[]|undefined if(__SSR__& & isInSSRComponentSetup){ //ssr处理... } //oldValue声明多个source监听则初始化为数组 letoldValue:any=isMultiSource ?newArray((sourceas[]).length).fill(INITIAL_WATCHER_VALUE) :INITIAL_WATCHER_VALUE //调度器调用时执行 constjob:SchedulerJob=()=> { if(!effect.active){ return } if(cb){ //watch(source,cb) //获取newValue constnewValue=effect.run() if( deep|| forceTrigger|| (isMultiSource ?(newValueasany[]).some((v,i)=> hasChanged(v,(oldValueasany[])[i]) ) :hasChanged(newValue,oldValue))|| (__COMPAT__& & isArray(newValue)& & isCompatEnabled(DeprecationTypes.WATCH_ARRAY,instance)) ){ //cleanupbeforerunningcbagain if(cleanup){ //执行onCleanup传过来的函数 cleanup() } //调用cb参数为newValue、oldValue、onCleanup callWithAsyncErrorHandling(cb,instance,ErrorCodes.WATCH_CALLBACK,[ newValue, //passundefinedastheoldvaluewhenit'schangedforthefirsttime oldValue===INITIAL_WATCHER_VALUE ?undefined :isMultiSource& & oldValue[0]===INITIAL_WATCHER_VALUE ?[] :oldValue, onCleanup ]) //更新oldValue oldValue=newValue } } else{ //watchEffect effect.run() } } //important:markthejobasawatchercallbacksothatschedulerknows //itisallowedtoself-trigger(#1727) job.allowRecurse=!!cb letscheduler:EffectScheduler if(flush==='sync'){ //同步更新即每次响应式数据改变都会回调一次cb通常不使用 scheduler=jobasany//theschedulerfunctiongetscalleddirectly } elseif(flush==='post'){ //job放入pendingPostFlushCbs队列中 //pendingPostFlushCbs队列会在queue队列执行完毕后执行函数更新effect通常会放在queue队列中 //所以pendingPostFlushCbs队列执行时组件已经更新完毕 scheduler=()=> queuePostRenderEffect(job,instance& & instance.suspense) } else{ //default:'pre' job.pre=true if(instance)job.id=instance.uid //默认异步更新关于异步更新会和nextTick放在一起详细讲解 scheduler=()=> queueJob(job) } //创建effecteffect.run的时候建立effect与getter内响应式数据的关系 consteffect=newReactiveEffect(getter,scheduler) if(__DEV__){ effect.onTrack=onTrack effect.onTrigger=onTrigger } //initialrun if(cb){ if(immediate){ //立马执行一次job job() } else{ //否则执行effect.run()会执行getter获取oldValue oldValue=effect.run() } } elseif(flush==='post'){ queuePostRenderEffect( effect.run.bind(effect), instance& & instance.suspense ) } else{ effect.run() } //返回一个取消监听的函数 constunwatch=()=> { effect.stop() if(instance& & instance.scope){ remove(instance.scope.effects!,effect) } } if(__SSR__& & ssrCleanup)ssrCleanup.push(unwatch) returnunwatch }
到此,关于“Vue3 computed和watch怎么实现”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!
声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!
若转载请注明出处: Vue3 computed和watch怎么实现
本文地址: https://pptw.com/jishu/301632.html