我們給effect函數(shù)加入lazy屬性职辅,來實(shí)現(xiàn)懶執(zhí)行厌均,在有些場景下唬滑,我們并不希望它立即執(zhí)行,而是希望在需要的時候才執(zhí)行棺弊,例如計算屬性间雀,我們在options里面添加lazy屬性來達(dá)到目的,即如下:
effect(
()=>{console.log(obj.foo)},
{
lazy:true
}
)
lazy選項和之前介紹的scheduler一樣镊屎,它通過options選項對象指定惹挟,有了它,我們就可以修改effect函數(shù)實(shí)現(xiàn)邏輯缝驳,當(dāng)options.lazy為true時连锯,則不立即執(zhí)行副作用函數(shù):
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
}
我們想實(shí)現(xiàn)通過定義computed函數(shù)來實(shí)現(xiàn)計算,實(shí)現(xiàn)方法如下:
function computed(getter){
let value
// dirty用來標(biāo)識是否重新計算值
let dirty = true
const effectFn=effect(
getter,
{
lazy:true,
// 屬性值發(fā)生變化需要dirty值變?yōu)閠rue用狱,重新計算
scheduler(){
dirty = true
}
}
)
const obj = {
get value(){
if(dirty){
value = effectFn()
// 將dirty設(shè)置為false运怖,下一次訪問直接使用緩存到value中的值
dirty = false
}
return value
}
}
return obj
}
使用時:
const sumRes = computed(()=>obj.bar+obj.foo)
console.log(sumRes.value)
但假如我們在外面這樣使用:
const sumRes = computed(()=>obj.bar+obj.foo)
effect(()=>{
console.log(sumRes.value)
})
obj.foo++
console.log(sumRes.value)
那么effect函數(shù)里還是打印2
原因:
從本質(zhì)上看這就是一個典型的 effect 嵌套。一個計算屬性內(nèi)部擁有自己的 effect夏伊,并且它是懶執(zhí)行的摇展,只有當(dāng)真正讀取計算屬性的值時才會執(zhí)行。對于計算屬性的 getter 函數(shù)來說溺忧,它里面訪問的響應(yīng)式數(shù)據(jù)只會把computed 內(nèi)部的 effect 收集為依賴咏连。而當(dāng)把計算屬性用于另外一個 effect 時盯孙,就會發(fā)生 effect 嵌套,外層的 effect 不會被內(nèi)層 effect 中的響應(yīng)式數(shù)據(jù)收集祟滴。
解決辦法:
我們可以手動調(diào)用 track 函數(shù)進(jìn)行追蹤振惰;當(dāng)計算屬性依賴的響應(yīng)式數(shù)據(jù)發(fā)生變化時,我們可以手動調(diào)用trigger 函數(shù)觸發(fā)響應(yīng):
function computed(getter){
let value
let dirty = true
const effectFn=effect(
getter,
{
lazy:true,
scheduler(){
if(!dirty){
dirty = true
// 當(dāng)計算屬性依賴響應(yīng)式數(shù)據(jù)變化時垄懂,手動調(diào)用trigger函數(shù)
trigger(obj,'value')
}
}
}
)
const obj = {
get value(){
if(dirty){
value = effectFn()
dirty = false
}
// 當(dāng)讀取value時骑晶,手動調(diào)用track函數(shù)追蹤
track(obj,'value')
return value
}
}
return obj
}
最后整體代碼如下:可自行測試
const data={foo:1,bar:1}
let activeEffect;
const bucket = new WeakMap()
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 cleanup(effectFn){
for(let i=0;i<effectFn.deps.length;i++){
effectFn.deps[i].delete(effectFn)
}
effectFn.deps.length = 0
}
function track(target,key){
if(!activeEffect)return
let depsMap = bucket.get(target)
if(!depsMap){bucket.set(target,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(target,key){
let depsMap = bucket.get(target)
if(!depsMap) return
const effects = depsMap.get(key)
const effectsToRun = new Set()
effects && effects.forEach(effectFn=>{
if(effectFn!==activeEffect){
effectsToRun.add(effectFn)
}
})
// 調(diào)度執(zhí)行
effectsToRun.forEach(effectFn=>{
if(effectFn.options.scheduler){
effectFn.options.scheduler(effectFn)
}else{
effectFn()
}
})
}
const obj = new Proxy(data,{
get(target,key){
track(target,key)
return target[key]
},
set(target,key,newVal){
target[key] = newVal
trigger(target,key)
}
})
function computed(getter){
let value
let dirty = true
const effectFn=effect(
getter,
{
lazy:true,
scheduler(){
if(!dirty){
dirty = true
// 當(dāng)計算屬性依賴響應(yīng)式數(shù)據(jù)變化時,手動調(diào)用trigger函數(shù)
trigger(obj,'value')
}
}
}
)
const obj = {
get value(){
if(dirty){
value = effectFn()
dirty = false
}
// 當(dāng)讀取value時草慧,手動調(diào)用track函數(shù)追蹤
track(obj,'value')
return value
}
}
return obj
}
const sumRes = computed(()=>obj.bar+obj.foo)
effect(
()=>{
console.log(sumRes.value)
}
)
obj.foo++
console.log(sumRes.value)