您好,登錄后才能下訂單哦!
本篇內容主要講解“vue3中effect與computed兩者之間的聯系”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“vue3中effect與computed兩者之間的聯系”吧!
在我剛看完vue3響應式的時候,心中就有一個不可磨滅的謎團,讓我茶不思飯不想,總想生病。那么這個謎團是什么呢?就是在響應式中一直穿行在tranger跟track之間的effect。如果單純的響應式原理根本就用不上effect,那么effect到底是干什么的呢?
船到橋頭自然直,柳岸花明又一村。苦心人天不負,偶然間我看到了effect測試代碼用例!
it('should observe basic properties', () => { let dummy const counter = reactive({ num: 0 }) effect(() => (dummy = counter.num)) expect(dummy).toBe(0) counter.num = 7 expect(dummy).toBe(7) })
解釋一下,這段代碼
首先聲明dummy變量,然后在effect的回調中把已響應的對象counter的num屬性賦值給dummy
然后做斷言判斷 dummy是否等于 0
將 counter.num 賦值 7 ,然后 dummy 也變成了 7 !
這,,,讓我想到了什么??
這就是computed的嗎?
趕緊看下 computed 的測試用例?。?/p>
const value = reactive<{ foo?: number }>({}) const cValue = computed(() => value.foo) expect(cValue.value).toBe(undefined) value.foo = 1 expect(cValue.value).toBe(1)
哈哈哈
阿哈哈哈哈
hhhhhhhhhhhhhhhhhhhh
忍不住想仰天長嘯??!
果然跟我猜想的一樣?。?!我終于直到effect是個什么鬼了,顧名思義effect是副作用的意思,也就是說它是響應式副產品,每次觸發(fā)了 get 時收集effect,每次set時在觸發(fā)這些effects。這樣就可以做一些響應式數據之外的一些事情了,比如計算屬性computed。
讓我們用effect實現一個computed 可能會更清晰一點
我就不寫一些亂七八糟的判斷了,讓大家能夠看的更加清楚
function computed (fn) { let value = undefined const runner = effect(fn, { // 如果lazy不置為true的話,每次創(chuàng)建effect的時候都會立即執(zhí)行一次 // 而我們要實現computed顯然是不需要的 lazy: true }) // 為什么要使用對象的形式,是因為我們最后需要得到computed的值 // 如果不用對象的 get 方法的話我們就需要手動再調用一次 computed() return { get value() { return runner() } } } // 使用起來是這樣的 const value = reactive({}) const cValue = computed(() => value.foo) value.foo = 1 console.log(cValue.value) // 1
這也太簡單了吧,那么重點來了,effect怎么實現的呢?
別著急,我們先捋一下邏輯
首先 如果 effect 回調內有已響應的對象被觸發(fā)了 get 時,effect就應該被儲存起來
然后,我們需要一個儲存effect的地方,在effect函數調用的時候就應該把effect放進這個儲存空間,在vue中使用的是一個數組activeReactiveEffectStack = []
再后,每個target被觸發(fā)的時候,都可能有多個effect,所以每個target需要有一個對應的依賴收集器 deps,等到 set 時遍歷 deps 執(zhí)行 effect()
然而,這個依賴收集器 deps 不能放在 target 本身上,這樣會使數據看起來不是很簡潔,還會存在多余無用的數據,所以我們需要一個 map 集合來儲存 target 跟 deps 的關系, 在vue中這個儲存集合叫 targetMap 。
幾個概念
track 追蹤器,在 get 時調用該函數,將所有 get 的 target 跟 key 以及 effect 建立起對應關系
// 比如 const react = reactive({a: { b: 2 }) // react.a 時 target -> {a: { b: 2 } key -> a // targetMap 儲存了 target --> Map --> key --> Set --> dep --> effect // 當調用 react.a.b.c.d.e 時 depsMap // {"a" => Set(1)} --> Set --> effect // {"b" => Set(1)} // {"c" => Set(1)} // {"d" => Set(1)} // {"e" => Set(1)} export function track(target: any, key: string) { const effect = activeReactiveEffectStack[activeReactiveEffectStack.length - 1]; if (effect) { let depsMap = targetMap.get(target); if (depsMap === void 0) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key!); if (!dep) { depsMap.set(key!, (dep = new Set())); } if (!dep.has(effect)) { dep.add(effect); effect.deps.push(dep); } } }
trigger 觸發(fā)器,這個就比較好理解了,拿到target key下的對應的所有 effect,然后遍歷執(zhí)行 effect()
export function trigger(target: any, key?: string | symbol) { const depsMap: any = targetMap.get(target); const effects: any = new Set() if (depsMap && depsMap.get(key)) { depsMap.get(key).forEach((dep: any) => { effects.add(dep) }); effects.forEach((e: any) => e()) } }
effect 函數實現
// 暴露的 effect 函數 export function effect( fn: Function, options: any = EMPTY_OBJ ): any { if ((fn as any).isEffect) { fn = (fn as any).raw } const effect = createReactiveEffect(fn, options) // 如果不是 lazy,則會立即執(zhí)行一次 if (!options.lazy) { effect() } return effect } // 創(chuàng)建 effect function createReactiveEffect( fn: Function, options: any ): any { const effect = function effect(...args: any): any { return run(effect as any, fn, args) } as any effect.isEffect = true effect.active = true effect.raw = fn effect.scheduler = options.scheduler effect.onTrack = options.onTrack effect.onTrigger = options.onTrigger effect.onStop = options.onStop effect.computed = options.computed effect.deps = [] return effect } // 執(zhí)行函數,執(zhí)行完之后會將儲存的 effect 刪除 // 這是函數 effect 的所有執(zhí)行,所經歷的完整的聲明周期 function run(effect: any, fn: Function, args: any[]): any { if (!effect.active) { return fn(...args) } if (activeReactiveEffectStack.indexOf(effect) === -1) { try { activeReactiveEffectStack.push(effect) return fn(...args) } finally { activeReactiveEffectStack.pop() } } }
一口氣寫了這么多,最后總結一下。在大家看源碼的時候,如果發(fā)現有哪個地方無從下手的話,可以先從測試用例開始看。因為測試用例可以很清楚的知道這個函數想要達到什么效果,然后從效果上想,為什么這么做,如果我自己寫的話應該怎么寫,這樣一點點就能揣摩出作者的意圖了。再根據源碼結合自己的想法你就能夠學到很多。
到此,相信大家對“vue3中effect與computed兩者之間的聯系”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。