這篇文章主要介紹“Vue3 computed和watch怎么實現(xiàn)”,在日常操作中,相信很多人在Vue3 computed和watch怎么實現(xiàn)問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Vue3 computed和watch怎么實現(xiàn)”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
創(chuàng)新互聯(lián)致力于互聯(lián)網(wǎng)網(wǎng)站建設(shè)與網(wǎng)站營銷,提供網(wǎng)站制作、網(wǎng)站設(shè)計、網(wǎng)站開發(fā)、seo優(yōu)化、網(wǎng)站排名、互聯(lián)網(wǎng)營銷、微信小程序開發(fā)、公眾號商城、等建站開發(fā),創(chuàng)新互聯(lián)網(wǎng)站建設(shè)策劃專家,為不同類型的客戶提供良好的互聯(lián)網(wǎng)應(yīng)用定制解決方案,幫助客戶在新的全球化互聯(lián)網(wǎng)環(huán)境中保持優(yōu)勢。
computed和watch在面試中經(jīng)常被問到他們的區(qū)別,那么我們就從源碼的實現(xiàn)來看看他們的具體實現(xiàn)
// packages/reactivity/src/computed.ts export function computed( getterOrOptions: ComputedGetter | WritableComputedOptions , debugOptions?: DebuggerOptions, isSSR = false ) { let getter: ComputedGetter let setter: ComputedSetter const onlyGetter = isFunction(getterOrOptions) if (onlyGetter) { getter = getterOrOptions setter = __DEV__ ? () => { console.warn('Write operation failed: computed value is readonly') } : NOOP } else { getter = getterOrOptions.get setter = getterOrOptions.set } // new ComputedRefImpl const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR) if (__DEV__ && debugOptions && !isSSR) { cRef.effect.onTrack = debugOptions.onTrack cRef.effect.onTrigger = debugOptions.onTrigger } // 返回ComputedRefImpl實例 return cRef as any }
可以看到computed內(nèi)部只是先處理getter和setter,然后new一個ComputedRefImpl返回,如果你知道ref API的實現(xiàn),可以發(fā)現(xiàn)他們的實現(xiàn)有很多相同之處
// packages/reactivity/src/computed.ts export class ComputedRefImpl{ public dep?: Dep = undefined // 存儲effect的集合 private _value!: T public readonly effect: ReactiveEffect public readonly __v_isRef = true public readonly [ReactiveFlags.IS_READONLY]: boolean = false public _dirty = true // 是否需要重新更新value public _cacheable: boolean constructor( getter: ComputedGetter , private readonly _setter: ComputedSetter , isReadonly: boolean, isSSR: boolean ) { // 創(chuàng)建effect this.effect = new ReactiveEffect(getter, () => { // 調(diào)度器執(zhí)行 重新賦值_dirty為true if (!this._dirty) { this._dirty = true // 觸發(fā)effect triggerRefValue(this) } }) // 用于區(qū)分effect是否是computed this.effect.computed = this this.effect.active = this._cacheable = !isSSR this[ReactiveFlags.IS_READONLY] = isReadonly } get value() { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 // computed ref可能被其他代理包裝,例如readonly() #3376 // 通過toRaw()獲取原始值 const self = toRaw(this) // 收集effect trackRefValue(self) // 如果是臟的,重新執(zhí)行effect.run(),并且將_dirty設(shè)置為false if (self._dirty || !self._cacheable) { self._dirty = false // run()方法會執(zhí)行g(shù)etter方法 值會被緩存到self._value self._value = self.effect.run()! } return self._value } set value(newValue: T) { this._setter(newValue) } }
可以看到ComputedRefImplget的get實現(xiàn)基本和ref的get相同(不熟悉ref實現(xiàn)的請看上一章),唯一的區(qū)別就是_dirty值的判斷,這也是我們常說的computed會緩存value,那么computed是如何知道value需要更新呢?
可以看到在computed構(gòu)造函數(shù)中,會建立一個getter與其內(nèi)部響應(yīng)式數(shù)據(jù)的關(guān)系,這跟我們組件更新函數(shù)跟響應(yīng)式數(shù)據(jù)建立關(guān)系是一樣的,所以與getter相關(guān)的響應(yīng)式數(shù)據(jù)發(fā)生修改的時候,就會觸發(fā)getter effect 對應(yīng)的scheduler,這里會將_dirty設(shè)置為true并去執(zhí)行收集到的effect(這里通常是執(zhí)行g(shù)et里收集到的函數(shù)更新的effect),然后就會去執(zhí)行函數(shù)更新函數(shù),里面會再次觸發(fā)computed的get,此時dirty已經(jīng)被置為true,就會重新執(zhí)行g(shù)etter獲取新的值返回,并將該值緩存到_vlaue。
所以computed是有兩層的響應(yīng)式處理的,一層是computed.value和函數(shù)的effect之間的關(guān)系(與ref的實現(xiàn)相似),一層是computed的getter和響應(yīng)式數(shù)據(jù)的關(guān)系。
注意:如果你足夠細心就會發(fā)現(xiàn)函數(shù)更新函數(shù)的effect觸發(fā)和computed getter的effect的觸發(fā)之間可能存在順序的問題。假如有一個響應(yīng)式數(shù)據(jù)a不僅存在于getter中,還在函數(shù)render中早于getter被訪問,此時a對應(yīng)的dep中更新函數(shù)的effect就會早于getter的effect被收集,如果此時a被改變,就會先執(zhí)行更新函數(shù)的effect,那么此時render函數(shù)訪問到computed.value的時候就會發(fā)現(xiàn)_dirty依然是false,因為getter的effect還沒有被執(zhí)行,那么此時依然會是舊值。vue3中對此的處理是執(zhí)行effects的時候會優(yōu)先執(zhí)行computed對應(yīng)的effect(此前章節(jié)也有提到):
// packages/reactivity/src/effect.ts export function triggerEffects( dep: Dep | ReactiveEffect[], debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { // spread into array for stabilization const effects = isArray(dep) ? dep : [...dep] // computed的effect會先執(zhí)行 // 防止render獲取computed值得時候_dirty還沒有置為true for (const effect of effects) { if (effect.computed) { triggerEffect(effect, debuggerEventExtraInfo) } } for (const effect of effects) { if (!effect.computed) { triggerEffect(effect, debuggerEventExtraInfo) } } }
watch相對于computed要更簡單一些,因為他只用建立getter與響應(yīng)式數(shù)據(jù)之間的關(guān)系,在響應(yīng)式數(shù)據(jù)變化時調(diào)用用戶傳過來的回調(diào)并將新舊值傳入即可
// packages/runtime-core/src/apiWatch.ts export function watch= false>( source: T | WatchSource , cb: any, options?: WatchOptions ): WatchStopHandle { if (__DEV__ && !isFunction(cb)) { warn(...) } // watch 具體實現(xiàn) return doWatch(source as any, cb, options) }
function doWatch( source: WatchSource | WatchSource[] | WatchEffect | object, cb: WatchCallback | null, { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ ): WatchStopHandle { if (__DEV__ && !cb) { ... } const warnInvalidSource = (s: unknown) => { warn(...) } const instance = getCurrentScope() === currentInstance?.scope ? currentInstance : null // const instance = currentInstance let getter: () => any let forceTrigger = false let isMultiSource = false // 根據(jù)不同source 創(chuàng)建不同的getter函數(shù) // getter 函數(shù)與computed的getter函數(shù)作用類似 if (isRef(source)) { getter = () => source.value forceTrigger = isShallow(source) } else if (isReactive(source)) { // source是reactive對象時 自動開啟deep=true getter = () => source deep = true } else if (isArray(source)) { isMultiSource = true forceTrigger = source.some(s => isReactive(s) || isShallow(s)) getter = () => source.map(s => { if (isRef(s)) { return s.value } else if (isReactive(s)) { return traverse(s) } else if (isFunction(s)) { return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER) } else { __DEV__ && warnInvalidSource(s) } }) } else if (isFunction(source)) { if (cb) { // getter with cb getter = () => callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER) } else { // no cb -> simple effect getter = () => { if (instance && instance.isUnmounted) { return } if (cleanup) { cleanup() } return callWithAsyncErrorHandling( source, instance, ErrorCodes.WATCH_CALLBACK, [onCleanup] ) } } } else { getter = NOOP __DEV__ && warnInvalidSource(source) } // 2.x array mutation watch compat // 兼容vue2 if (__COMPAT__ && cb && !deep) { const baseGetter = getter getter = () => { const val = baseGetter() if ( isArray(val) && checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance) ) { traverse(val) } return val } } // 深度監(jiān)聽 if (cb && deep) { const baseGetter = getter // traverse會遞歸遍歷對象的所有屬性 以達到深度監(jiān)聽的目的 getter = () => traverse(baseGetter()) } let cleanup: () => void // watch回調(diào)的第三個參數(shù) 可以用此注冊一個cleanup函數(shù) 會在下一次watch cb調(diào)用前執(zhí)行 // 常用于競態(tài)問題的處理 let onCleanup: OnCleanup = (fn: () => void) => { cleanup = effect.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 eager or sync flush let ssrCleanup: (() => void)[] | undefined if (__SSR__ && isInSSRComponentSetup) { // ssr處理 ... } // oldValue 聲明 多個source監(jiān)聽則初始化為數(shù)組 let oldValue: any = isMultiSource ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE) : INITIAL_WATCHER_VALUE // 調(diào)度器調(diào)用時執(zhí)行 const job: SchedulerJob = () => { if (!effect.active) { return } if (cb) { // watch(source, cb) // 獲取newValue const newValue = effect.run() if ( deep || forceTrigger || (isMultiSource ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])[i]) ) : hasChanged(newValue, oldValue)) || (__COMPAT__ && isArray(newValue) && isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)) ) { // cleanup before running cb again if (cleanup) { // 執(zhí)行onCleanup傳過來的函數(shù) cleanup() } // 調(diào)用cb 參數(shù)為newValue、oldValue、onCleanup callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [ newValue, // pass undefined as the old value when it's changed for the first time oldValue === INITIAL_WATCHER_VALUE ? undefined : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE ? [] : oldValue, onCleanup ]) // 更新oldValue oldValue = newValue } } else { // watchEffect effect.run() } } // important: mark the job as a watcher callback so that scheduler knows // it is allowed to self-trigger (#1727) job.allowRecurse = !!cb let scheduler: EffectScheduler if (flush === 'sync') { // 同步更新 即每次響應(yīng)式數(shù)據(jù)改變都會回調(diào)一次cb 通常不使用 scheduler = job as any // the scheduler function gets called directly } else if (flush === 'post') { // job放入pendingPostFlushCbs隊列中 // pendingPostFlushCbs隊列會在queue隊列執(zhí)行完畢后執(zhí)行 函數(shù)更新effect通常會放在queue隊列中 // 所以pendingPostFlushCbs隊列執(zhí)行時組件已經(jīng)更新完畢 scheduler = () => queuePostRenderEffect(job, instance && instance.suspense) } else { // default: 'pre' job.pre = true if (instance) job.id = instance.uid // 默認(rèn)異步更新 關(guān)于異步更新會和nextTick放在一起詳細講解 scheduler = () => queueJob(job) } // 創(chuàng)建effect effect.run的時候建立effect與getter內(nèi)響應(yīng)式數(shù)據(jù)的關(guān)系 const effect = new ReactiveEffect(getter, scheduler) if (__DEV__) { effect.onTrack = onTrack effect.onTrigger = onTrigger } // initial run if (cb) { if (immediate) { // 立馬執(zhí)行一次job job() } else { // 否則執(zhí)行effect.run() 會執(zhí)行g(shù)etter 獲取oldValue oldValue = effect.run() } } else if (flush === 'post') { queuePostRenderEffect( effect.run.bind(effect), instance && instance.suspense ) } else { effect.run() } // 返回一個取消監(jiān)聽的函數(shù) const unwatch = () => { effect.stop() if (instance && instance.scope) { remove(instance.scope.effects!, effect) } } if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch) return unwatch }
到此,關(guān)于“Vue3 computed和watch怎么實現(xiàn)”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
本文題目:Vue3computed和watch怎么實現(xiàn)
標(biāo)題網(wǎng)址:http://weahome.cn/article/pggpoe.html