整體結(jié)論
reactive做數(shù)據(jù)代理和負(fù)責(zé)依賴收集的觸發(fā)
effect 做數(shù)據(jù)副作用的觸發(fā)函數(shù)粉怕,函數(shù)內(nèi)部自運行effect()觸發(fā)reactive的get
track get的觸發(fā)做effectFn的快照收集,結(jié)構(gòu)用weakMap保存(下面詳細(xì)介紹),依賴收集已經(jīng)做好
trigger 當(dāng)原始值刪除或者改變觸發(fā)reactive中set和deleteProperty執(zhí)行trigger架专,他把weakMap中的effectFn找出來然后重新執(zhí)行一遍
tips:建議與下面簡版源碼一起食用
weakMap中的數(shù)據(jù)結(jié)構(gòu)
單獨擰出來說,搞懂這個基本搞懂50%~
const obj = reactive({ a: 'a', b: { c: 1 } })//測試數(shù)據(jù)玄帕,生成的weakMap如下
三層結(jié)構(gòu):weakMap ==>Map ==>Set
-
weakMap:存儲對象和key的關(guān)系
- key:target對象部脚,對應(yīng)的是effect中引用的層級深度,也就是說{ a: 'a', b: { c: 1 } }一個 { c: 1 }兩個 為什么是這樣設(shè)計的呢裤纹?因為proxy的回調(diào)target就是原始對象委刘,也在做深度響應(yīng)的時候丧没,通過對象去找也只會觸發(fā)一次trigger()
- vaue:Map
-
Map:存儲key和effectFn的關(guān)系
- key:對應(yīng)的每個層級的key a/b c
- value:Set
-
Set:存儲對應(yīng)的effect函數(shù)
- value:effect傳進來的函數(shù)做存儲
下面直接貼代碼~
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="aDom"></div>
<div id="bDom"></div>
<script>
//數(shù)據(jù)響應(yīng)式
function reactive(obj) {
if (!isObject(obj)) {
return obj
}
return new Proxy(obj, {
get(target, key) {
//Reflect容錯率不錯,錯誤會正確的處理
const res = Reflect.get(target, key)
console.log('get', res)
track(target, key)
return isObject(res) ? reactive(res) : res
},
set(target, key, value) {
const res = Reflect.set(target, key, value)
console.log('set', key)
trigger(target, key)
return res
},
deleteProperty(target, key) {
const res = Reflect.deleteProperty(target, key)
console.log('del', key)
trigger(target, key)
return res
}
})
}
function isObject(obj) {
return typeof obj === 'object'
}
// 保存函數(shù)
const effectStack = []
//保存映射關(guān)系的數(shù)據(jù)結(jié)構(gòu)
const targetMap = new WeakMap()
//觸發(fā)依賴收集
function effect(fn) {
//對照源碼簡化的高階函數(shù)锡移,可以從簡呕童,但是為了原滋原味
const e = createReactiveEffect(fn)
//自運行
e()
return e
}
function createReactiveEffect(fn) {
//錯誤處理
//effectStack 進棧
//執(zhí)行完依賴收集后出棧保證effectStack最后值的獲取
const effectFn = function () {
try {
effectStack.push(effectFn)
fn()
} finally {
effectStack.pop()
}
}
return effectFn
}
//跟蹤函數(shù):負(fù)責(zé)依賴收集
function track(target, key) {
//1. 獲取effectFn
const effect = effectStack[effectStack.length - 1]
if (effect) {
//用過target獲取對應(yīng)的map 每一層級的數(shù)據(jù)對應(yīng)一個targetMap target:Map
let depMap = targetMap.get(target)
//首次進入depMap為空,需要創(chuàng)建
if (!depMap) {
depMap = new Map()
targetMap.set(target, depMap)
}
// 2. 通過key獲取依賴集合set
let deps = depMap.get(key)
if (!deps) {
deps = new Set()
depMap.set(key, deps)
}
// 3. 放入effect
deps.add(effect)
//層級關(guān)系是 obj:{key:effect} 三層套娃 WeakMap ==> Map ==>Set
}
}
//觸發(fā)函數(shù):track()相反操作,拿出映射關(guān)系淆珊,執(zhí)行所有的cbs
function trigger(target, key) {
const depMap = targetMap.get(target)
if (!depMap) return
const deps = depMap.get(key)
if (deps) {
deps.forEach(dep => dep())
}
}
const obj = reactive({ a: 'a', b: { c: 1 } })
//源碼中 這里做虛擬dom的path夺饲,這里簡單的用textContent改變頁面上的值做響應(yīng)式
effect(() => {
aDom.textContent = obj.a
console.log('effect1', obj.a)
})
//深層次的副作用
effect(() => {
bDom.textContent = obj.b.c
console.log('effect2', obj.b.c)
})
setTimeout(() => {
obj.a = '777'
//當(dāng)改變深度子節(jié)點 只會觸發(fā)一次trigger 因為weakMap是以obj的target(遞歸中就是每一層的對象)存儲的
obj.b.c = "666"
}, 2000)
</script>
</body>
</html>
都看到這里了~三連我就不要了,點個贊就行~哈哈