Vue利用廣度優(yōu)先搜索實現(xiàn)watch
前言
通過前面幾篇文章,我們對Vue3中的響應式設計有了初步的了解。
- 面試官:Vue3響應式系統(tǒng)都不會寫,還敢說精通?
- 面試官:你覺得Vue的響應式系統(tǒng)僅僅是一個Proxy?
- Vue3:原來你是這樣的“異步更新”
- 為啥面試官總喜歡問computed是咋實現(xiàn)的?
這一篇我們試著實現(xiàn)一個watch
1. 兩種watch的基本用法
1.1 通過函數(shù)回調監(jiān)聽數(shù)據(jù)
最基本的用法是給watch指定一個回調函數(shù)并返回你想要監(jiān)聽的響應式數(shù)據(jù)。
const?state1?=?reactive({
??name:?'前端胖頭魚',
??age:?100
})
watch(()?=>?state1.age,?()?=>?{
??console.log('state1的age發(fā)生變化了',?state1.age)
})
state1.age?=?200
setTimeout(()?=>?{
??state1.age?=?300
},?500)
1.2 直接監(jiān)聽一個對象
還可以直接監(jiān)聽一個響應式對象來觀測它的變化。
const?state1?=?reactive({
??name:?'前端胖頭魚',
??age:?100,
??children:?{
????name:?'胖小魚',
????age:?10
??}
})
watch(state1,?()?=>?{
??console.log('state1發(fā)生變化了',?state1)
})
state1.age?=?200
setTimeout(()?=>?{
??state1.children.age?=?100
},?500)
2. 實現(xiàn)watch最核心的點
其實watch的底層實現(xiàn)非常簡單,和computed一樣都需要借助任務調度。
簡單來說就是感知數(shù)據(jù)的變化,數(shù)據(jù)發(fā)生了變化就執(zhí)行對應的回調,那么怎么感知呢?
const?state?=?reactive({
??name:?'前端胖頭魚'
})
useEffect(()?=>?{
??//?原本state發(fā)生變化之后,應該執(zhí)行這里
??console.log(state.name)
},?{
??//?但是指定scheduler之后,會執(zhí)行這里
??scheduler?()?{
????console.log('state變化了')
??}
})
state.name?=?'胖小魚'聰明的你肯定也猜到了,scheduler不就是天然感知數(shù)據(jù)的變化的工具嗎?
沒錯,watch的實現(xiàn)少不了它,來吧,搞起!??!
3. 支持兩種使用方式
3.1 支持回調函數(shù)形式
const?watch?=?(source,?cb)?=>?{
??effect(source,?{
????scheduler?()?{
??????cb()
????},
??})
}
//?測試一波
const?state?=?reactive({
??name:?'前端胖頭魚',
})
watch(()?=>?state.name,?()?=>?{
??console.log('state.name發(fā)生了變化',?state.name)
})
state.name?=?'胖小魚'
3.2 支持直接傳遞響應式對象
不錯哦!第一種方式已經(jīng)初步實現(xiàn)了,接下來搞第二種。
第二種直接傳入響應式對象的方式和第一種傳入回調函數(shù)并指向響應式數(shù)據(jù)的區(qū)別是什么?
在于我們需要手動遍歷這個響應式對象使得它的任意屬性發(fā)生變化我們都能感知到。
3.3 廣度優(yōu)先搜索遍歷深層嵌套的屬性
此時就到了這篇文章裝逼(額~~)的點了。如果想訪問一個深層嵌套對象的所有屬性,最常見的做法就是遞歸。
如果你想在面試的過程中秀一波,我覺得使用廣度優(yōu)先搜索是個不錯的主意(狗頭臉),代碼也非常簡單,就不詳細解釋了。
如果您對廣度優(yōu)先搜索和深度優(yōu)先搜索感興趣歡迎在評論區(qū)留言,我會單獨寫一篇文章來講它。
?const?bfs?=?(obj,?callback)?=>?{
??const?queue?=?[?obj?]
??while?(queue.length)?{
????const?top?=?queue.shift()
????if?(top?&&?typeof?top?===?'object')?{
??????for?(let?key?in?top)?{
????????//?讀取操作出發(fā)getter,完成依賴搜集
????????queue.push(top[?key?])
??????}
????}?else?{
??????callback?&&?callback(top)
????}
??}
}
const?obj?=?{
??name:?'前端胖頭魚',
??age:?100,
??obj2:?{
????name:?'胖小魚',
????age:?10,
????obj3:?{
??????name:?'胖小小魚',
??????age:?1,
????}
??},
}
bfs(obj,?(value)?=>?{
??console.log(value)
})
我們已經(jīng)能夠讀取深層嵌套對象的任意屬性了,接下來繼續(xù)完善watch方法
const?watch?=?(source,?cb)?=>?{
??let?getter
??//?處理傳回調的方式
??if?(typeof?source?===?"function")?{
????getter?=?source
??}?else?{
????//?封裝成讀取source對象的函數(shù),觸發(fā)任意一個屬性的getter,進而搜集依賴
????getter?=?()?=>?bfs(source)
??}
??const?effectFn?=?effect(getter,?{
????scheduler()?{
??????cb()
????}
??})
}
//?測試一波
const?state?=?reactive({
??name:?"前端胖頭魚",
??age:?100,
??obj2:?{
????name:?"胖小魚",
????age:?10,
??},
})
watch(state,?()?=>?{
??console.log("state發(fā)生變化了");
});
看來還是有不少坑??!雖然我們實現(xiàn)了n層嵌套對象屬性的讀?。ɡ碚撋纤械膶傩愿淖兌紤撚|發(fā)回調),但是state.obj2.name = 'yyyy'卻沒有被感知到,為什么呢?
3.4 淺響應與深響應
回顧一下reactive函數(shù),你會發(fā)現(xiàn),當value本身也是一個對象的時候,我們并不會使value也變成一個響應式數(shù)據(jù)。
所以哪怕我們通過bfs方法遍歷了該對象的所有屬性,也僅僅是第一層的key具有了響應式效果而已。
//?統(tǒng)一對外暴露響應式函數(shù)
function?reactive(state)?{
??return?new?Proxy(state,?{
????get(target,?key)?{
??????const?value?=?target[key]
??????//?搜集key的依賴
??????//?如果value本身是一個對象,對象下的屬性將不具有響應式
??????track(target,?key)?
??????return?value;
????},
????set(target,?key,?newValue)?{
??????//?console.log(`set?${key}:?${newValue}`)
??????//?設置屬性值
??????target[key]?=?newValue
??????trigger(target,?key)
????},
??})
}解決辦法也很簡單,是對象的情況下再給他reactive一次就好了。
//?統(tǒng)一對外暴露響應式函數(shù)
function?reactive(state)?{
??return?new?Proxy(state,?{
????get(target,?key)?{
??????const?value?=?target[key]
??????//?搜集key的依賴
??????//?如果value本身是一個對象,對象下的屬性將不具有響應式
??????track(target,?key)?
??????//?如果是對象,再使其也變成一個響應式數(shù)據(jù)
??????if?(typeof?value?===?"object"?&&?value?!==?null)?{
????????return?reactive(value);
??????}
??????return?value;
????},
????set(target,?key,?newValue)?{
??????//?console.log(`set?${key}:?${newValue}`)
??????//?設置屬性值
??????target[key]?=?newValue
??????trigger(target,?key)
????},
??})
}最后再回到前面的例子,你會發(fā)現(xiàn)我們成功了!!!

4. watch的新值和舊值
到目前為止,我們實現(xiàn)了watch最基本的功能,感知其數(shù)據(jù)的變化并執(zhí)行對應的回調。
接下來我們再實現(xiàn)一個基礎功能:在回調函數(shù)中獲取新值與舊值。
watch(state,?(newVal,?oldVal)?=>?{
??//?xxx
})新值和舊值主要在于獲取時機不一樣,獲取方式確實一模一樣的,執(zhí)行effectFn即可
const?watch?=?(source,?cb)?=>?{
??let?getter
??let?oldValue
??let?newValue
??//?處理傳回調的方式
??if?(typeof?source?===?"function")?{
????getter?=?source
??}?else?{
????getter?=?()?=>?bfs(source)
??}
??const?effectFn?=?effect(getter,?{
????lazy:?true,
????scheduler()?{
??????//?變化后獲取新值
??????newValue?=?effectFn()
??????cb(newValue,?oldValue)
??????//?執(zhí)行回調后將新值設置為舊值
??????oldValue?=?newValue
????}
??})
??//?第一次執(zhí)行獲取值
??oldValue?=?effectFn()
}測試一波
const?state?=?reactive({
??name:?"前端胖頭魚",
??age:?100,
??obj2:?{
????name:?"胖小魚",
????age:?10,
??},
})
watch(()?=>?state.name,?(newValue,?oldValue)?=>?{
??console.log("state.name",?{?newValue,?oldValue?})
})
state.name?=?'111'
5. 支持立即調用時機
最后再實現(xiàn)立即調用時機immediate,watch就大功告成啦!
const?watch?=?(source,?cb,?options?=?{})?=>?{
??let?getter
??let?oldValue
??let?newValue
??//?處理傳回調的方式
??if?(typeof?source?===?"function")?{
????getter?=?source
??}?else?{
????getter?=?()?=>?bfs(source)
??}
??const?job?=?()?=>?{
????//?變化后獲取新值
????newValue?=?effectFn()
????cb(newValue,?oldValue)
????//?執(zhí)行回調后將新值設置為舊值
????oldValue?=?newValue
??}
??const?effectFn?=?effect(getter,?{
????lazy:?true,
????scheduler()?{
??????job()
????}
??})
??//?如果指定了立即執(zhí)行,便執(zhí)行第一次
??if?(options.immediate)?{
????job()
??}?else?{
????oldValue?=?effectFn()
??}
}
watch(()?=>?state.name,?(newValue,?oldValue)?=>?{
??console.log("state.name",?{?newValue,?oldValue?});
},?{?immediate:?true?});
通過判斷immediate是否為true來決定是否一開始就執(zhí)行cb回調,且第一次回調的舊值oldValue應該為undefined。
以上就是Vue利用廣度優(yōu)先搜索實現(xiàn)watch的詳細內(nèi)容,更多關于Vue watch的資料請關注腳本之家其它相關文章!
相關文章
Vue?Echarts報錯Initialize?failed:?invalid?dom解決方法
最近因為工作需要,用到了ECharts做圖表,也遇到了問題,就來跟大家總結分享一下,下面這篇文章主要給大家介紹了關于Vue?Echarts報錯Initialize?failed:?invalid?dom的解決方法,需要的朋友可以參考下2023-06-06
vue中使用iframe嵌入網(wǎng)頁,頁面可自適應問題
這篇文章主要介紹了vue中使用iframe嵌入網(wǎng)頁,頁面可自適應問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09

