成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

手寫 Vue3 響應式系統:實現 Computed

開發 前端
我們通過標記是否 dirty 來實現緩存,當 sheduler 執行的時候,說明數據變了,把 dirty 置為 true,重新計算 computed 的值,否則直接拿緩存。

??上篇文章??我們實現了基本的響應式系統,這篇文章繼續實現 computed。

首先,我們簡單回顧一下:

響應式系統的核心就是一個 WeakMap --- Map --- Set 的數據結構。

圖片

WeakMap 的 key 是原對象,value 是響應式的 Map。這樣當對象銷毀的時候,對應的 Map 也會銷毀。

Map 的 key 就是對象的每個屬性,value 是依賴這個對象屬性的 effect 函數的集合 Set。

然后用 Proxy 代理對象的 get 方法,收集依賴該對象屬性的 effect 函數到對應 key 的 Set 中。

還要代理對象的 set 方法,修改對象屬性的時候調用所有該 key 的 effect 函數。

上篇文章我們按照這樣的思路實現了一個比較完善的響應式系統,然后今天繼續實現 computed。

實現 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 內部也是這樣實現的。

責任編輯:武曉燕 來源: 神光的編程秘籍
相關推薦

2021-12-02 05:50:35

Vue3 插件Vue應用

2022-06-23 07:46:34

VueMobx系統

2021-12-08 09:09:33

Vue 3 Computed Vue2

2020-06-09 11:35:30

Vue 3響應式前端

2021-09-27 06:29:47

Vue3 響應式原理Vue應用

2022-07-14 08:22:48

Computedvue3

2023-12-11 07:34:37

Computed計算屬性Vue3

2022-01-19 18:05:47

Vue3前端代碼

2023-02-06 08:39:01

PreactVue3響應式

2025-02-17 08:58:06

2024-05-27 08:39:17

Vue3變量響應式

2023-12-06 07:43:56

Vue如何定義事件

2024-03-08 10:38:07

Vue響應式數據

2024-07-08 08:43:19

2022-12-06 08:39:27

Vue3Reactive

2022-03-10 11:04:04

Vue3Canvas前端

2021-12-01 08:11:44

Vue3 插件Vue應用

2021-11-30 08:19:43

Vue3 插件Vue應用

2023-11-28 09:03:59

Vue.jsJavaScript

2021-05-19 14:25:19

前端開發技術
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 五月婷婷导航 | 国产精品久久久久久一区二区三区 | 久久久99精品免费观看 | 亚洲精品久久久一区二区三区 | 正在播放一区二区 | 91爱爱·com| 国产a视频| 久久久久久久久国产成人免费 | 国产成人精品综合 | 精品一区二区视频 | 武道仙尊动漫在线观看 | 99reav| 欧美一区二区三区四区视频 | 亚洲3p| 天堂一区二区三区 | 国产精品一区二区在线 | 一区二区三区四区不卡视频 | 狠狠操在线 | 久久国产婷婷国产香蕉 | 皇色视频在线 | 91亚洲国产 | 青青久草 | 亚欧精品一区 | 日本免费一区二区三区 | 国产精品久久久久久久久久久久 | 日韩和的一区二区 | 天天综合网天天综合 | 欧美黄色网 | 成人午夜免费福利视频 | av片在线观看网站 | 97精品超碰一区二区三区 | 日韩免费视频一区二区 | 天天噜天天干 | 成人自拍av | 二区高清 | 欧美国产日韩一区二区三区 | 日本天堂一区二区 | 久久久入口 | 一级片免费视频 | 久久精品免费看 | 人成在线|