<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
上篇文章我們實現了基本的響應式系統,這篇文章繼續實現 computed。
首先,我們簡單回顧一下:
響應式系統的核心就是一個 WeakMap --- Map --- Set 的資料結構。
WeakMap 的 key 是原物件,value 是響應式的 Map。這樣當物件銷燬的時候,對應的 Map 也會銷燬。
Map 的 key 就是物件的每個屬性,value 是依賴這個物件屬性的 effect 函數的集合 Set。然後用 Proxy 代理物件的 get 方法,收集依賴該物件屬性的 effect 函數到對應 key 的 Set 中。還要代理物件的 set 方法,修改物件屬性的時候呼叫所有該 key 的 effect 函數。
上篇文章我們按照這樣的思路實現了一個比較完善的響應式系統,然後今天繼續實現 computed。
首先,我們把之前的程式碼重構一下,把依賴收集和觸發依賴函數的執行抽離成 track 和 trigger 函數:
邏輯還是新增 effect 到對應的 Set,以及觸發對應 Set 裡的 effect 函數執行,但抽離出來清晰多了。
然後繼續實現 computed。
computed 的使用大概是這樣的:
const value = computed(() => { return obj.a + obj.b; });
對比下 effect:
effect(() => { console.log(obj.a); });
區別只是多了個返回值。
所以我們基於 effect 實現 computed 就是這樣的:
function computed(fn) { const value = effect(fn); return value }
當然,現在的 effect 是沒有返回值的,要給它加一下:
只是在之前執行 effect 函數的基礎上把返回值記錄下來返回,這個改造還是很容易的。
現在 computed 就能返回計算後的值了:
但是現在資料一遍,所有的 effect 都執行了,而像 computed 這裡的 effect 是沒必要每次都重新執行的,只需要在資料變了之後執行。
所以我們新增一個 lazy 的 option 來控制 effect 不立刻執行,而是把函數返回讓使用者自己執行。
然後 computed 裡用 effect 的時候就新增一個 lazy 的 option,讓 effect 函數不執行,而是返回出來。
computed 裡建立一個物件,在 value 的 get 觸發時呼叫該函數拿到最新的值:
我們測試下:
可以看到現在 computed 返回值的 value 屬性是能拿到計算後的值的,並且修改了 obj.a. 之後會重新執行計算函數,再次拿 value 時能拿到新的值。
只是多執行了一次計算,這是因為 obj.a 變的時候會執行所有的 effect 函數:
這樣每次資料變了都會重新執行 computed 的函數來計算最新的值。
這是沒有必要的,effect 的函數是否執行應該也是可以控制的。所以我們要給它加上排程的功能:
可以支援傳入 schduler 回撥函數,然後執行 effect 的時候,如果有 scheduler 就傳給它讓使用者自己來排程,否則才執行 effect 函數。
這樣使用者就可以自己控制 effect 函數的執行了:
然後再試一下剛才的程式碼:
可以看到,obj.a 變了之後並沒有執行 effect 函數來重新計算,因為我們加了 sheduler 來自己排程。這樣就避免了資料變了以後馬上執行 computed 函數,可以自己控制執行。
現在還有一個問題,每次存取 res.value 都要計算:
能不能加個快取呢?只有資料變了才需要計算,否則直接拿之前計算的值。
當然是可以的,加個標記就行:
scheduler 被呼叫的時候就說明資料變了,這時候 dirty 設定為 true,然後取 value 的時候就重新計算,之後再改為 false,下次取 value 就直接拿計算好的值了。
我們測試下:
我們存取 computed 值的 value 屬性時,第一次會重新計算,後面就直接拿計算好的值了。
修改它依賴的資料後,再次存取 value 屬性會再次重新計算,然後後面再存取就又會直接拿計算好的值了。
至此,我們完成了 computed 的功能。
但現在的 computed 實現還有一個問題,比如這樣一段程式碼:
let res = computed(() => { return obj.a + obj.b; }); effect(() => { console.log(res.value); });
我們在一個 effect 函數裡用到了 computed 值,按理說 obj.a 變了,那 computed 的值也會變,應該觸發所有的 effect 函數。
但實際上並沒有:
這是為什麼呢?
這是因為返回的 computed 值並不是一個響應式的物件,需要把它變為響應式的,也就是 get 的時候 track 收集依賴,set 的時候觸發依賴的執行:
我們再試一下:
現在 computed 值變了就能觸發依賴它的 effect 了。至此,我們的 computed 就很完善了。
完整程式碼如下:
const data = { a: 1, b: 2 } let activeEffect const effectStack = []; function effect(fn, options = {}) { const effectFn = () => { cleanup(effectFn) activeEffect = effectFn effectStack.push(effectFn); const res = fn() effectStack.pop() activeEffect = effectStack[effectStack.length - 1] return res } effectFn.deps = [] effectFn.options = options; if (!options.lazy) { effectFn() } return effectFn } function computed(fn) { let value let dirty = true const effectFn = effect(fn, { lazy: true, scheduler(fn) { if(!dirty) { dirty = true trigger(obj, 'value'); } } }); const obj = { get value() { if (dirty) { value = effectFn() dirty = false } track(obj, 'value'); console.log(obj); return value } } return obj } function cleanup(effectFn) { for (let i = 0; i < effectFn.deps.length; i++) { const deps = effectFn.deps[i] deps.delete(effectFn) } effectFn.deps.length = 0 } const reactiveMap = new WeakMap() const obj = new Proxy(data, { get(targetObj, key) { track(targetObj, key); return targetObj[key] }, set(targetObj, key, newVal) { targetObj[key] = newVal trigger(targetObj, key) } }) function track(targetObj, key) { let depsMap = reactiveMap.get(targetObj) if (!depsMap) { reactiveMap.set(targetObj, (depsMap = new Map())) } let deps = depsMap.get(key) if (!deps) { depsMap.set(key, (deps = new Set())) } deps.add(activeEffect) activeEffect.deps.push(deps); } function trigger(targetObj, key) { const depsMap = reactiveMap.get(targetObj) if (!depsMap) return const effects = depsMap.get(key) const effectsToRun = new Set(effects) effectsToRun.forEach(effectFn => { if(effectFn.options.scheduler) { effectFn.options.scheduler(effectFn) } else { effectFn() } }) }
上篇文章我們實現了響應式的核心資料結構,依賴的收集、資料變化後通知依賴函數執行。今天我們在那基礎上實現了 computed。我們改造了 effect 函數,讓它返回傳入的 fn,然後在 computed 裡自己執行來拿到計算後的值。
我們又支援了 lazy 和 scheduler 的 option,lazy 是讓 effect 不立刻執行傳入的函數,scheduler 是在資料變動觸發依賴執行的時候回撥 sheduler 來排程。
我們通過標記是否 dirty 來實現快取,當 sheduler 執行的時候,說明資料變了,把 dirty 置為 true,重新計算 computed 的值,否則直接拿快取。此外,computed 的 value 並不是響應式物件,我們需要單獨的呼叫下 track 和 trigger。這樣,我們就實現了完善的 computed 功能,vue3 內部也是這樣實現的。
到此這篇關於 Vue3 響應式系統實現 computed的文章就介紹到這了,更多相關 Vue3 computed內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45