Vue利用廣度優(yōu)先搜索實(shí)現(xiàn)watch
前言
通過前面幾篇文章,我們對Vue3中的響應(yīng)式設(shè)計有了初步的了解。
- 面試官:Vue3響應(yīng)式系統(tǒng)都不會寫,還敢說精通?
- 面試官:你覺得Vue的響應(yīng)式系統(tǒng)僅僅是一個Proxy?
- Vue3:原來你是這樣的“異步更新”
- 為啥面試官總喜歡問computed是咋實(shí)現(xiàn)的?
這一篇我們試著實(shí)現(xiàn)一個watch
1. 兩種watch的基本用法
1.1 通過函數(shù)回調(diào)監(jiān)聽數(shù)據(jù)
最基本的用法是給watch
指定一個回調(diào)函數(shù)并返回你想要監(jiān)聽的響應(yīng)式數(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)聽一個響應(yīng)式對象來觀測它的變化。
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. 實(shí)現(xiàn)watch最核心的點(diǎn)
其實(shí)watch
的底層實(shí)現(xiàn)非常簡單,和computed
一樣都需要借助任務(wù)調(diào)度
。
簡單來說就是感知數(shù)據(jù)的變化,數(shù)據(jù)發(fā)生了變化就執(zhí)行對應(yīng)的回調(diào),那么怎么感知呢?
const?state?=?reactive({ ??name:?'前端胖頭魚' }) useEffect(()?=>?{ ??//?原本state發(fā)生變化之后,應(yīng)該執(zhí)行這里 ??console.log(state.name) },?{ ??//?但是指定scheduler之后,會執(zhí)行這里 ??scheduler?()?{ ????console.log('state變化了') ??} }) state.name?=?'胖小魚'
聰明的你肯定也猜到了,scheduler不就是天然感知數(shù)據(jù)的變化的工具嗎?
沒錯,watch
的實(shí)現(xiàn)少不了它,來吧,搞起?。。?/p>
3. 支持兩種使用方式
3.1 支持回調(diào)函數(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 支持直接傳遞響應(yīng)式對象
不錯哦!第一種方式已經(jīng)初步實(shí)現(xiàn)了,接下來搞第二種。
第二種直接傳入響應(yīng)式對象的方式和第一種傳入回調(diào)函數(shù)并指向響應(yīng)式數(shù)據(jù)的區(qū)別是什么?
在于我們需要手動遍歷這個響應(yīng)式對象使得它的任意屬性發(fā)生變化我們都能感知到。
3.3 廣度優(yōu)先搜索遍歷深層嵌套的屬性
此時就到了這篇文章裝逼(額~~)的點(diǎn)了。如果想訪問一個深層嵌套對象的所有屬性,最常見的做法就是遞歸。
如果你想在面試的過程中秀一波,我覺得使用廣度優(yōu)先搜索是個不錯的主意(狗頭臉),代碼也非常簡單,就不詳細(xì)解釋了。
如果您對廣度優(yōu)先搜索和深度優(yōu)先搜索感興趣歡迎在評論區(qū)留言,我會單獨(dú)寫一篇文章來講它。
?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 ??//?處理傳回調(diào)的方式 ??if?(typeof?source?===?"function")?{ ????getter?=?source ??}?else?{ ????//?封裝成讀取source對象的函數(shù),觸發(fā)任意一個屬性的getter,進(jìn)而搜集依賴 ????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ā)生變化了"); });
看來還是有不少坑??!雖然我們實(shí)現(xiàn)了n層嵌套對象屬性的讀?。ɡ碚撋纤械膶傩愿淖兌紤?yīng)該觸發(fā)回調(diào)),但是state.obj2.name = 'yyyy'
卻沒有被感知到,為什么呢?
3.4 淺響應(yīng)與深響應(yīng)
回顧一下reactive
函數(shù),你會發(fā)現(xiàn),當(dāng)value
本身也是一個對象的時候,我們并不會使value
也變成一個響應(yīng)式數(shù)據(jù)。
所以哪怕我們通過bfs
方法遍歷了該對象的所有屬性,也僅僅是第一層的key
具有了響應(yīng)式效果而已。
//?統(tǒng)一對外暴露響應(yīng)式函數(shù) function?reactive(state)?{ ??return?new?Proxy(state,?{ ????get(target,?key)?{ ??????const?value?=?target[key] ??????//?搜集key的依賴 ??????//?如果value本身是一個對象,對象下的屬性將不具有響應(yīng)式 ??????track(target,?key)? ??????return?value; ????}, ????set(target,?key,?newValue)?{ ??????//?console.log(`set?${key}:?${newValue}`) ??????//?設(shè)置屬性值 ??????target[key]?=?newValue ??????trigger(target,?key) ????}, ??}) }
解決辦法也很簡單,是對象的情況下再給他reactive
一次就好了。
//?統(tǒng)一對外暴露響應(yīng)式函數(shù) function?reactive(state)?{ ??return?new?Proxy(state,?{ ????get(target,?key)?{ ??????const?value?=?target[key] ??????//?搜集key的依賴 ??????//?如果value本身是一個對象,對象下的屬性將不具有響應(yīng)式 ??????track(target,?key)? ??????//?如果是對象,再使其也變成一個響應(yīng)式數(shù)據(jù) ??????if?(typeof?value?===?"object"?&&?value?!==?null)?{ ????????return?reactive(value); ??????} ??????return?value; ????}, ????set(target,?key,?newValue)?{ ??????//?console.log(`set?${key}:?${newValue}`) ??????//?設(shè)置屬性值 ??????target[key]?=?newValue ??????trigger(target,?key) ????}, ??}) }
最后再回到前面的例子,你會發(fā)現(xiàn)我們成功了!!!
4. watch的新值和舊值
到目前為止,我們實(shí)現(xiàn)了watch
最基本的功能,感知其數(shù)據(jù)的變化并執(zhí)行對應(yīng)的回調(diào)。
接下來我們再實(shí)現(xiàn)一個基礎(chǔ)功能:在回調(diào)函數(shù)中獲取新值與舊值。
watch(state,?(newVal,?oldVal)?=>?{ ??//?xxx })
新值和舊值主要在于獲取時機(jī)不一樣,獲取方式確實(shí)一模一樣的,執(zhí)行effectFn即可
const?watch?=?(source,?cb)?=>?{ ??let?getter ??let?oldValue ??let?newValue ??//?處理傳回調(diào)的方式 ??if?(typeof?source?===?"function")?{ ????getter?=?source ??}?else?{ ????getter?=?()?=>?bfs(source) ??} ??const?effectFn?=?effect(getter,?{ ????lazy:?true, ????scheduler()?{ ??????//?變化后獲取新值 ??????newValue?=?effectFn() ??????cb(newValue,?oldValue) ??????//?執(zhí)行回調(diào)后將新值設(shè)置為舊值 ??????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. 支持立即調(diào)用時機(jī)
最后再實(shí)現(xiàn)立即調(diào)用時機(jī)immediate
,watch
就大功告成啦!
const?watch?=?(source,?cb,?options?=?{})?=>?{ ??let?getter ??let?oldValue ??let?newValue ??//?處理傳回調(diào)的方式 ??if?(typeof?source?===?"function")?{ ????getter?=?source ??}?else?{ ????getter?=?()?=>?bfs(source) ??} ??const?job?=?()?=>?{ ????//?變化后獲取新值 ????newValue?=?effectFn() ????cb(newValue,?oldValue) ????//?執(zhí)行回調(diào)后將新值設(shè)置為舊值 ????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
回調(diào),且第一次回調(diào)的舊值oldValue
應(yīng)該為undefined
。
以上就是Vue利用廣度優(yōu)先搜索實(shí)現(xiàn)watch的詳細(xì)內(nèi)容,更多關(guān)于Vue watch的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue?Echarts報錯Initialize?failed:?invalid?dom解決方法
最近因?yàn)楣ぷ餍枰?用到了ECharts做圖表,也遇到了問題,就來跟大家總結(jié)分享一下,下面這篇文章主要給大家介紹了關(guān)于Vue?Echarts報錯Initialize?failed:?invalid?dom的解決方法,需要的朋友可以參考下2023-06-06vue 的keep-alive緩存功能的實(shí)現(xiàn)
本篇文章主要介紹了vue 的keep-alive緩存功能的實(shí)現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03vue中使用iframe嵌入網(wǎng)頁,頁面可自適應(yīng)問題
這篇文章主要介紹了vue中使用iframe嵌入網(wǎng)頁,頁面可自適應(yīng)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09Vue.js組件實(shí)現(xiàn)選項(xiàng)卡以及切換特效
這篇文章主要為大家詳細(xì)介紹了Vue.js組件實(shí)現(xiàn)選項(xiàng)卡以及切換特效,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-07-07vue中父子組件相互傳值的實(shí)現(xiàn)方法詳解
父子組件通信是Vue中常見的場景,這篇文章主要為大家詳細(xì)介紹了vue中父子組件相互傳值的實(shí)現(xiàn)方法,文中的示例代碼講解詳細(xì),需要的小伙伴可以參考一下2023-12-12vue3實(shí)現(xiàn)數(shù)字滾動特效實(shí)例詳解
這篇文章主要為大家介紹了vue3實(shí)現(xiàn)數(shù)字滾動特效實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09