functioncreateSetter(shallow =false){returnfunctionset(target: object,key: string | symbol,value: unknown,receiver: object): boolean {const oldValue =(target as any)[key]if(!shallow){// 通過setter入參的新值獲取它的原始值,新傳入值可能是響應式數據,如果直接和// target上的原始值比較是沒有任何實際意義的value =toRaw(value)// target不是數組,且舊值為ref,新值非ref,直接將ref.value更新為新值if(!isArray(target)&&isRef(oldValue)&&!isRef(value)){oldValue.value = valuereturntrue}}else{// in shallow mode, objects are set as-is regardless of reactive or not}const hadKey =hasOwn(target, key)const result = Reflect.set(target, key, value, receiver)// don't trigger if target is something up in the prototype chain of original// 這里其實就是為了判斷receiver是proxy實例還是原型鏈上有proxy的對象,只有前者會觸發// 更新派發,防止通過原型鏈觸發攔截器觸發更新。// 這里處理的非常巧妙,看下createGetter代碼,在getter handler中做了一層攔截,當訪問// __v_raw屬性時,只有receiver本身時proxy實例時才會返回target,即原始目標對象。再看// 下面的createSetter代碼,toRaw會訪問receiver的__v_raw屬性,從而觸發getter// handler,由于我們已經做了原型鏈訪問攔截,所以在setter里如果receiver位于原型鏈上,// 那么是訪問不到__v_raw屬性的,因此確保了只有receiver本身是proxy實例才觸發更新派發if(target ===toRaw(receiver)){// 區分是新增屬性和固有屬性if(!hadKey){trigger(target, TriggerOpTypes.ADD, key, value)}elseif(hasChanged(value, oldValue)){trigger(target, TriggerOpTypes.SET, key, value, oldValue)}}return result}}
trigger:
exportfunctiontrigger(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 trackedreturn}const effects =newSet<ReactiveEffect>()// 普通effect集合const computedRunners =newSet<ReactiveEffect>()// 計算effect集合// add方法用來將依賴Map中的對應dep Set添加到局部集合中,以便后續觸發(run)// add函數用于過濾出符合條件的依賴,用于批量觸發constadd=(effectsToAdd: Set<ReactiveEffect>| undefined)=>{if(effectsToAdd){effectsToAdd.forEach(effect =>{// effect和當前激活的effect引用相同,且shouldTrack = true時不會將// 遍歷的effect推入局部Set,shouldTrack為true表示開啟依賴收集模式,// 該模式下,activeEffect是當前正在收集中的依賴,依賴收集未完成不能// 派發該依賴的更新// effect不能和當前激活的effect相同,為了避免effect的無限觸發,比如// effect的getter回調里是foo.value++這種情況,首先foo.value會觸發// Proxy的get攔截器,track收集當前激活的effect,然后// foo.value = foo.value + 1會觸發Proxy的set攔截器,trigger剛剛收集到的// effect,然后effect執行getter回調,會繼續重復相同的過程(同一個effect track// -> trigger),陷入死循環,因此需要避免掉收集當前激活的effectif(effect !== activeEffect ||!shouldTrack){if(effect.options.computed){computedRunners.add(effect)}else{effects.add(effect)}}else{// the effect mutated its own dependency during its execution.// this can be caused by operations like foo.value++// do not trigger or we end in an infinite loop}})}}if(type === TriggerOpTypes.CLEAR){// collection being cleared// trigger all effects for target// 清空依賴時,將target對應的依賴map中的依賴全部添加到局部Set,準備triggerdepsMap.forEach(add)}elseif(key ==='length'&&isArray(target)){depsMap.forEach((dep, key)=>{// 源對象是數組且trigger的key是length,需要觸發length對應的effects,// 同時,由于觸發length時一定是數組長度變化了,當數組長度變短時,需要// 做已刪除數組元素的effects的trigger工作,也就是索引號 >= 數組// 最新length的元素們所對應的effects,對它們做清除后的批量觸發。if(key ==='length'|| key >=(newValue as number)){add(dep)}})}else{// schedule runs for SET | ADD | DELETE// key不是undefined就會觸發依賴局部添加,只不過如果是新增屬性對應的dep為空if(key !==void0){add(depsMap.get(key))}// also run for iteration key on ADD | DELETE | Map.SET// 對于新增屬性或者刪除屬性的處理const isAddOrDelete =type === TriggerOpTypes.ADD||(type === TriggerOpTypes.DELETE&&!isArray(target))// 新增或刪除屬性,如果是數組,會觸發length對應的依賴,如果書普通對象,會觸發// ITERATE_KEY對應的依賴;如果是Map設置屬性,會觸發ITERATE_KEYif(isAddOrDelete ||(type === TriggerOpTypes.SET&& target instanceofMap)){// Object新增屬性不會觸發getter,因此無依賴收集,需要觸發ITERATE_KEY收集的依賴add(depsMap.get(isArray(target)?'length':ITERATE_KEY))}// 對于Map新增或刪除屬性,統一觸發MAP_KEY_ITERATE_KEY所對應的依賴if(isAddOrDelete && target instanceofMap){add(depsMap.get(MAP_KEY_ITERATE_KEY))}}// run函數用來批量執行add方法添加過的effectconstrun=(effect: ReactiveEffect)=>{if(__DEV__ && effect.options.onTrigger){effect.options.onTrigger({effect,target,key,type,newValue,oldValue,oldTarget})}if(effect.options.scheduler){// 非立即執行的effect,即需要使用schedular調度器的effect,比如計算effecteffect.options.scheduler(effect)}else{// 立即執行的effect,無需特殊調度處理effect()}}// Important: computed effects must be run first so that computed getters// can be invalidated before any normal effects that depend on them are run.computedRunners.forEach(run)effects.forEach(run)}
functiondoWatch(source: WatchSource | WatchSource[]| WatchEffect | object,cb: WatchCallback |null,{ immediate, deep, flush, onTrack, onTrigger }: WatchOptions =EMPTY_OBJ,instance = currentInstance
): WatchStopHandle {constwarnInvalidSource=(s: unknown)=>{warn(`Invalid watch source: `,s,`A watch source can only be a getter/effect function, a ref, `+`a reactive object, or an array of these types.`)}// 根據數據源解析出對應的getterlet getter:()=> anylet forceTrigger =falseif(isRef(source)){// Refgetter=()=>(source as Ref).valueforceTrigger =!!(source as Ref)._shallow}elseif(isReactive(source)){// 響應式數據需要深度偵聽getter=()=> sourcedeep =true}elseif(isArray(source)){// 數據源數組,對數組中的各個數據源進行遍歷get,以達到依賴收集的目的getter=()=>source.map(s =>{if(isRef(s)){return s.value}elseif(isReactive(s)){// 遍歷子代數據收集依賴returntraverse(s)}elseif(isFunction(s)){returncallWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)}else{__DEV__ &&warnInvalidSource(s)}})}elseif(isFunction(source)){// 函數類型的數據源if(cb){// getter with cbgetter=()=>callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)}else{// no cb -> simple effectgetter=()=>{if(instance && instance.isUnmounted){return}if(cleanup){cleanup()}returncallWithErrorHandling(source,instance,ErrorCodes.WATCH_CALLBACK,[onInvalidate])}}}else{getter =NOOP__DEV__ &&warnInvalidSource(source)}if(cb && deep){// traverse就是將數據源的所有子代屬性全部get一遍,達到依賴被深層收集的目的const baseGetter = gettergetter=()=>traverse(baseGetter())}let cleanup:()=>voidconst onInvalidate: InvalidateCbRegistrator =(fn:()=>void)=>{cleanup = runner.options.onStop=()=>{callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)}}// in SSR there is no need to setup an actual effect, and it should be noop// unless it's eagerif(__NODE_JS__ && isInSSRComponentSetup){if(!cb){getter()}elseif(immediate){callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK,[getter(),undefined,onInvalidate])}returnNOOP}let oldValue =isArray(source)?[]:INITIAL_WATCHER_VALUEconst job:SchedulerJob=()=>{if(!runner.active){return}if(cb){// watch(source, cb)// 執行effect(runner)通過getter獲取到偵聽數據的最新值const newValue =runner()if(deep || forceTrigger ||hasChanged(newValue, oldValue)){// cleanup before running cb againif(cleanup){cleanup()}// 執行偵聽回調,并以新值、舊值、無效化函數作為入參callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK,[newValue,// pass undefined as the old value when it's changed for the first timeoldValue ===INITIAL_WATCHER_VALUE? undefined : oldValue,onInvalidate])// 更新舊值oldValue = newValue}}else{// watchEffectrunner()}}// important: mark the job as a watcher callback so that scheduler knows// it is allowed to self-trigger (#1727)// 標記任務是否允許自我觸發,有回調表示允許自我觸發job.allowRecurse =!!cblet scheduler: ReactiveEffectOptions['scheduler']// 通過調度配置項決定偵聽effect執行時機,可以為同步、延后、前置三種執行時機,通過effect// 的scheduler選項函數來控制if(flush ==='sync'){// 在數據源發生變化時,setter直接同步觸發偵聽effect對應的scheduler函數執行scheduler = job}elseif(flush ==='post'){// effect.scheduler實際為queuePostCb(調度器一節有介紹),也就是說數據源setter觸發時// 會通過queuePostCb將job先推入render effect后置任務隊列,在清空render effect主// 隊列后在批量執行后置任務隊列中的jobs,達到的效果就是偵聽任務在渲染任務全部執行完成// 后在進行調度批量執行scheduler=()=>queuePostRenderEffect(job, instance && instance.suspense)}else{// default: 'pre'// 效果和post相反,在渲染任務批量執行前進行批量清隊執行scheduler=()=>{if(!instance || instance.isMounted){queuePreFlushCb(job)}else{// with 'pre' option, the first call must happen before// the component is mounted so it is called synchronously.job()}}}// 偵聽effect主體,lazy模式,非立即執行const runner =effect(getter,{lazy:true,onTrack,onTrigger,scheduler})// 把偵聽effect記錄到組件實例的effect棧里recordInstanceBoundEffect(runner)// 初次執行if(cb){if(immediate){// 立即執行effect對應的job,也就是說會立即執行外部傳入的cb回調,// 同時執行runner effect獲取到最新的值作為cb入參,vue會在每次// 執行job()}else{// 非立即執行偵聽,只需要觸發effect getter的執行,完成偵聽依賴的收集oldValue =runner()}}elseif(flush ==='post'){queuePostRenderEffect(runner, instance && instance.suspense)}else{runner()}// watch返回執行停止偵聽與狀態清理的回調return()=>{// 停止當前的偵聽effectstop(runner)// 由于當前的runner effect已經處于非激活態,無效化的effect需要// 從對應的組件實例上移除掉if(instance){remove(instance.effects!, runner)}}}