vue開發(fā)設(shè)計分支切換與cleanup實例詳解
分支切換與cleanup
了解分支切換
代碼示例如下
const data = { ok: true, text: "hello world" }; function reactive(obj) { return new Proxy(obj, { get(target, key) { track(target, key); return target[key]; }, // 在set操作中,賦值,然后調(diào)用effect函數(shù) set(target, key, value) { target[key] = value; trigger(target, key); return true; }, }); } const obj = reactive(data); effect(function effectFn(){ document.body.innerText = obj.ok ? obj.text : "not"; });
當代碼字段obj.ok
發(fā)生變化時,代碼執(zhí)行的分支會跟著變化,這就是分支切換。
分支切換可能會產(chǎn)生遺留的副作用函數(shù)。
上面代碼中有個三元運算式,如果obj.ok = true
,則展示obj.text
,此時,effectFn
執(zhí)行會觸發(fā)obj.ok
和obj.text
的讀取操作,否則展示"not"
此時的依賴收集如下圖展示:
const data = { ok: true, text: "hello world" }; const obj = reactive(data); effect(function effectFn(){ document.body.innerText = obj.ok ? obj.text : "not"; });
分支切換導致的問題
當發(fā)生obj.ok
改變且為false
時,此時obj.text
對應的依賴effectFn
不會執(zhí)行,
但是obj.text
發(fā)生改變時,對應的effectFn
卻會執(zhí)行,頁面的內(nèi)容會被修改掉。這是不期望發(fā)生的!
此時,是key為ok對應的effectFn依舊有效,
key為text對應的effectFn為無效,應該清除掉,如下圖展示
如何清除掉副作用函數(shù)的無效關(guān)聯(lián)關(guān)系?
- 每次副作用函數(shù)執(zhí)行前,可以先把它從所有與之關(guān)聯(lián)的依賴集合中刪除,然后清空依賴集合的收集,
- 當副作用函數(shù)執(zhí)行,所有會重新建立關(guān)聯(lián)。(副作用函數(shù)中,會重新執(zhí)行響應式數(shù)據(jù)的get操作,從而進行收集依賴)
步驟:
- 副作用函數(shù)收集與自身關(guān)聯(lián)的依賴集合
在
effect
注冊副作用函數(shù)中為effectFn
增添一個屬性deps
,用來存儲依賴集合,在
track
函數(shù)中,進行依賴集合的收集- 將副作用函數(shù)從與之關(guān)聯(lián)的所有依賴集合中移除,
在
effect
注冊副作用函數(shù)中,觸發(fā)副作用函數(shù)前,清除副作用函數(shù)的依賴集合
疑問:為什么對傳入的副作用函數(shù)進行一層包裹?
- 為了對副作用函數(shù)進行更多操作,
- 為副作用函數(shù)增加deps屬性,作為收集依賴集合的容器
- 清除副作用函數(shù)的依賴集合
function effect(fn) { const effectFn = () => { activeFn = effectFn; cleanup(effectFn); fn(); }; effectFn.deps = []; effectFn(); } function cleanup(effectFn) { // 從副作用函數(shù)關(guān)聯(lián)的依賴集合中刪除副作用函數(shù),從而斷開關(guān)聯(lián) for (const deps of effectFn.deps) { deps.delete(effectFn); } // 重置effectFn.deps effectFn.deps.length = 0; } // 收集effectFn的依賴集合 function track(target, key) { if (!activeFn) return target[key]; let depMap = bucket.get(target); if (!depMap) { depMap = new Map(); bucket.set(target, depMap); } let deps = depMap.get(key); if (!deps) { deps = new Set(); depMap.set(key, deps); } deps.add(activeFn); // 收集effectFn的依賴集合 activeFn.deps.push(deps); }
完整代碼
// 響應式數(shù)據(jù)的基本實現(xiàn) let activeFn = undefined; const bucket = new WeakMap(); let times = 0; function reactive(obj) { return new Proxy(obj, { get(target, key) { console.log(target, key); if (times > 10) { throw "超出"; } times++; console.log(times); track(target, key); return target[key]; }, // 在set操作中,賦值,然后調(diào)用effect函數(shù) set(target, key, value) { target[key] = value; trigger(target, key); return true; }, }); } // 收集effectFn的依賴集合 function track(target, key) { console.log("track"); if (!activeFn) return target[key]; let depMap = bucket.get(target); if (!depMap) { depMap = new Map(); bucket.set(target, depMap); } let deps = depMap.get(key); if (!deps) { deps = new Set(); depMap.set(key, deps); } deps.add(activeFn); // 收集effectFn的依賴集合 activeFn.deps.push(deps); } function trigger(target, key) { const depMap = bucket.get(target); if (!depMap) return; const effects = depMap.get(key); if (!effects) return; effects.forEach((fn) => { fn(); }); } const data = { ok: true, text: "hello world" }; const obj = reactive(data); function effect(fn) { const effectFn = () => { activeFn = effectFn; cleanup(effectFn); fn(); }; effectFn.deps = []; effectFn(); } function cleanup(effectFn) { // 從副作用函數(shù)關(guān)聯(lián)的依賴集合中刪除副作用函數(shù),從而斷開關(guān)聯(lián) for (const deps of effectFn.deps) { deps.delete(effectFn); } // 重置effectFn.deps effectFn.deps.length = 0; } function effect0() { console.log("%cindex.js line:83 obj.text", "color: #007acc;", obj.text); } effect(effect0); obj.text = "hello vue";
產(chǎn)生的問題:代碼運行發(fā)生棧溢出
具體問題代碼:
obj.text = "hello vue"; // 觸發(fā)trigger函數(shù) function trigger(target, key) { ... // 調(diào)用包裝的副作用函數(shù) effects.forEach((fn) => { // 1.effects fn(); }); } // 上面的fn const effectFn = () => { activeFn = effectFn; // 把副作用函數(shù)從依賴集合中刪除 cleanup(effectFn); // 執(zhí)行副作用函數(shù),重新收集依賴 fn(); }; function cleanup(effectFn) { // 從副作用函數(shù)關(guān)聯(lián)的依賴集合中刪除副作用函數(shù),從而斷開關(guān)聯(lián) for (const deps of effectFn.deps) { // 此處的deps是上面的 1.effects // deps刪除effectFn // effects中的副作用函數(shù)減少 deps.delete(effectFn); } // 重置effectFn.deps effectFn.deps.length = 0; } function track(target, key) { ... // 此處的deps是上面的 1.effects // effects添加副作用函數(shù) deps.add(activeFn); // 收集effectFn的依賴集合 activeFn.deps.push(deps); }
- 當設(shè)置響應式對象的值時,觸發(fā)
trigger
函數(shù),遍歷依賴集合, - 遍歷的過程中,每個回合,被包裹的副作用函數(shù)執(zhí)行,
cleanup
,把副作用函數(shù)從依賴集合中刪除觸發(fā)副作用函數(shù)
副作用函數(shù)執(zhí)行觸發(fā)響應式數(shù)據(jù)的
get
操作,重新收集依賴函數(shù)- 繼續(xù)遍歷
所以: 在遍歷的過程中,每個回合刪除元素,增加元素,導致遍歷無法結(jié)束,導致棧溢出。
問題簡單用代碼展示如下:
const set = new Set([1]) set.forEach(item => { set.delete(1) set.add(1) console.log('遍歷中') })
如何解決此種情況下的棧溢出?
將遍歷effects
變成遍歷effects
的拷貝的值,不修改到efftcts就可以了
function trigger(target, key) { const depMap = bucket.get(target); if (!depMap) return; const effects = depMap.get(key); if (!effects) return; const effectsToRun = new Set(effects) effectsToRun.forEach((fn) => { fn(); }); }
嵌套的effect與effect棧
effect嵌套的場景?
在Vue中,Vue的渲染函數(shù)就是在一個effect中執(zhí)行的
主要的場景是:組件嵌套組件。
如果不支持effect嵌套,產(chǎn)生的后果
初始化
function effect(fn) { const effectFn = () => { activeFn = effectFn; activeFn.fnName = fn.name; console.log("fnName", activeFn.fnName); cleanup(effectFn); fn(); }; effectFn.deps = []; effectFn(); } effect(function effect1() { console.log("effect1"); effect(function effect2() { console.log("effect2", obj.text); }); console.log("effect1", obj.ok); }); // fnName effect1 // effect1 // fnName effect2 // effect2 hello world // effect1 true
obj.ok = false;
// fnName effect2 // effect2 hello world
原因:
- 執(zhí)行
effect(effect1)
代碼 - 執(zhí)行
effectFn
effectFn
函數(shù)中,activeFn
包裹的副作用函數(shù)為effect1
- 執(zhí)行
effect1
- 觸發(fā)了
effect(effect2)
,此時effect1
還沒有被收集 - 執(zhí)行
effectFn
effectFn
函數(shù)中,activeFn
包裹的副作用函數(shù)為effect2
- 執(zhí)行
effect2
effect2
被收集,effect2
執(zhí)行完成- 繼續(xù)執(zhí)行
effect1
,此時activeFn
包裹的副作用函數(shù)仍為effect2
- 所以此時收集的副作用函數(shù)又為
effect2
- 執(zhí)行
obj.ok = false;
- 遍歷對應的依賴集合,觸發(fā)
effect2
支持嵌套
- 需要把正在執(zhí)行,且沒有執(zhí)行完的被包裹的副作用函數(shù)存入棧中
- 當最上面的被包裹的副作用函數(shù)執(zhí)行完,彈出
const effectStack = []; function effect(fn) { const effectFn = () => { activeFn = effectFn; cleanup(effectFn); // 把當前執(zhí)行的函數(shù)壓入棧中 effectStack.push(effectFn); fn(); // 函數(shù)執(zhí)行完畢,彈出 effectStack.pop(); // activeFn賦值為還未執(zhí)行完的副作用函數(shù) activeFn = effectStack[effectStack.length - 1]; }; effectFn.deps = []; effectFn(); }
避免無限遞歸循環(huán)
產(chǎn)生無限遞歸循環(huán)的代碼:
const data = {foo : 1} const obj = reactive(data) effect(()=> obj.foo++)
原因分析:
() => { obj.foo = obj.foo + 1 }
obj.foo
在讀取自身之后又設(shè)置自身
- 讀取
obj.foo
會觸發(fā)track
track
收集依賴后,然后繼續(xù)執(zhí)行上面的賦值操作- 設(shè)置
obj.foo
會觸發(fā)trigger
- 然后遍歷依賴集合,再次觸發(fā)
obj.foo
的讀取 - 循環(huán)
解決循環(huán)
- 設(shè)置和讀取是在一個副作用函數(shù)中進行的,都是
activeEffect
- 如果
trigger
觸發(fā)執(zhí)行的副作用函數(shù)與當前正在執(zhí)行的副作用函數(shù)相同,則不觸發(fā)執(zhí)行
function trigger(target, key) { const depMap = bucket.get(target); if (!depMap) return; const effects = depMap.get(key); if (!effects) return; const effectsToRun = new Set(); effects.forEach((fn) => { if (fn !== activeFn) { // 當觸發(fā)的fn與當前執(zhí)行的副作用函數(shù)不同時 // 將fn添加到effectsToRun effectsToRun.add(fn); } }); effectsToRun.forEach((fn) => { fn(); }); }
完整代碼
// 響應式數(shù)據(jù)的基本實現(xiàn) let activeFn = undefined; const bucket = new WeakMap(); // 副作用函數(shù)調(diào)用棧 const effectStack = []; function reactive(obj) { return new Proxy(obj, { get(target, key) { track(target, key); return target[key]; }, // 在set操作中,賦值,然后調(diào)用effect函數(shù) set(target, key, value) { target[key] = value; trigger(target, key); return true; }, }); } // 收集effectFn的依賴集合 function track(target, key) { if (!activeFn) return target[key]; let depMap = bucket.get(target); if (!depMap) { depMap = new Map(); bucket.set(target, depMap); } let deps = depMap.get(key); if (!deps) { deps = new Set(); depMap.set(key, deps); } deps.add(activeFn); // 收集effectFn的依賴集合 activeFn.deps.push(deps); } function trigger(target, key) { const depMap = bucket.get(target); if (!depMap) return; const effects = depMap.get(key); if (!effects) return; const effectsToRun = new Set(); effects.forEach((fn) => { if (fn !== activeFn) { // 當觸發(fā)的fn與當前執(zhí)行的副作用函數(shù)不同時 // 將fn添加到effectsToRun effectsToRun.add(fn); } }); effectsToRun.forEach((fn) => { if (fn.options.scheduler) { fn.options.scheduler(fn); } else { fn(); } }); } const data = { ok: true, text: "hello world" }; const obj = reactive(data); function effect(fn) { const effectFn = () => { activeFn = effectFn; cleanup(effectFn); // 把當前執(zhí)行的函數(shù)壓入棧中 effectStack.push(effectFn); fn(); // 函數(shù)執(zhí)行完畢,彈出 effectStack.pop(); // activeFn賦值為還未執(zhí)行完的副作用函數(shù) activeFn = effectStack[effectStack.length - 1]; }; effectFn.deps = []; effectFn(); } function cleanup(effectFn) { // 從副作用函數(shù)關(guān)聯(lián)的依賴集合中刪除副作用函數(shù),從而斷開關(guān)聯(lián) for (const deps of effectFn.deps) { deps.delete(effectFn); } // 重置effectFn.deps effectFn.deps.length = 0; } function effect0() { console.log("%cindex.js line:83 obj.text", "color: #007acc;", obj.text); } effect(effect0); obj.text = "hello vue";
以上就是vue開發(fā)設(shè)計分支切換與cleanup實例詳解的詳細內(nèi)容,更多關(guān)于vue開發(fā)設(shè)計分支切換cleanup的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue3+Vue Router實現(xiàn)動態(tài)路由導航的示例代碼
隨著單頁面應用程序(SPA)的日益流行,前端開發(fā)逐漸向復雜且交互性強的方向發(fā)展,在這個過程中,Vue.js及其生態(tài)圈的工具(如Vue Router)為我們提供了強大的支持,本文將介紹如何在Vue 3中使用Vue Router實現(xiàn)動態(tài)路由導航,需要的朋友可以參考下2024-08-08解決vue數(shù)據(jù)更新但table內(nèi)容不更新的問題
這篇文章主要給大家介紹了vue數(shù)據(jù)更新table內(nèi)容不更新解決方法,文中有詳細的代碼示例供大家作為參考,感興趣的同學可以參考閱讀一下2023-08-08Vue Element前端應用開發(fā)之動態(tài)菜單和路由的關(guān)聯(lián)處理
這篇文章主要介紹了Vue Element前端應用開發(fā)之動態(tài)菜單和路由的關(guān)聯(lián)處理,對vue感興趣的同學,可以參考下2021-05-05vue動態(tài)生成新表單并且添加驗證校驗規(guī)則方式
這篇文章主要介紹了vue動態(tài)生成新表單并且添加驗證校驗規(guī)則方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10vue依賴包報錯問題eslint\lib\cli-engine\cli-engine.js:421
這篇文章主要介紹了vue依賴包報錯問題eslint\lib\cli-engine\cli-engine.js:421,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08VUE如何利用vue-print-nb實現(xiàn)打印功能詳解
這篇文章主要給大家介紹了關(guān)于VUE如何利用vue-print-nb實現(xiàn)打印功能的相關(guān)資料,文中還給大家介紹了vue-print-nb使用中的常見問題,如空白頁,需要的朋友可以參考下2022-04-04