vue3解读—reactivity响应式实现
前言:Vue3 中引入了proxy進(jìn)行數(shù)據(jù)劫持,而effect是響應(yīng)式系統(tǒng)的核心,而響應(yīng)式系統(tǒng)又是 vue3 中的核心,所以vue3的解讀要從 effect 開始講起。
1.reactivity和effect的使用
目前vue3的各個模塊都可以單獨安裝,首先我們需要安裝npm i @vue/reactivity,從中引入我們需要的方法,下面是我簡單寫一段測試代碼。
const { reactive, effect } = require("@vue/reactivity"); let a = reactive({value: 1, }); let b; effect(() => {b = a.value + 10;console.log(b); }); a.value = 10;先別急著看我的輸出,想想自己心中的答案是什么?
下面公布控制臺的實際輸出:
總結(jié):我們會發(fā)現(xiàn)effect函數(shù)執(zhí)行了兩次,一次是我們往effect函數(shù)傳入匿名函數(shù)時,它立即執(zhí)行了一次;接下來是我們對響應(yīng)式變量a.value進(jìn)行重新賦值時(或者說effect內(nèi)函數(shù)所依賴的響應(yīng)式變量發(fā)生變化時)它又執(zhí)行了一次。
接下來我們就好好思考下,如何設(shè)計一個reactive方法,將傳入的值變成響應(yīng)式,以及實現(xiàn)一個effect函數(shù),其內(nèi)函數(shù)所依賴的響應(yīng)式數(shù)據(jù)變化時,該函數(shù)會再次執(zhí)行。
2.Dep和effect的實現(xiàn)
reactive會將傳入的變量變成響應(yīng)式數(shù)據(jù),包括對象等數(shù)據(jù);地基需要從下往上一層一層的搭建,我們這里先實現(xiàn)將單一變量編譯成響應(yīng)式數(shù)據(jù),并用effct對其監(jiān)聽。廢話不多說,上代碼:
第一步:
class Dep {constructor(val) {this._val = val;}get value() {return this._val;}set value(newVal) {this._val = newVal} } const dep = new Dep(10); console.log('取值:', dep.value); dep.value = 20; console.log('賦值:', dep.value);我們通過class類去創(chuàng)建一個簡單的變量,用其構(gòu)造器constructor初始化對象屬性,使用setter和getter存取器攔截該屬性的存取行為。此時dep就是響應(yīng)式數(shù)據(jù)嗎?想啥呢!但我們會很熟悉后面的取存值行為,這不就是我們在vue3中使用ref聲明變量時,取值和賦值的寫法嗎!
有了這一步的鋪墊,我們接下來的思路是不是更清晰了,只需要在初始化一個Dep時,收集依賴該dep的函數(shù),并在dep值發(fā)生變化時,再次執(zhí)行這個依賴函數(shù),是不是就能實現(xiàn)前面reactive和effect所達(dá)到的效果。ready go!
第二步:
let currentEffect; class Dep {constructor(val) {this.effects = new Set(); // 儲存依賴當(dāng)前變量的函數(shù),并去重this._val = val;}get value() {this.depend();return this._val;}set value(newVal) {this._val = newValthis.notice(); // 賦值時觸發(fā)依賴}// 收集依賴depend() {if(currentEffect) { // 要記得判空this.effects.add(currentEffect);}}// 觸發(fā)依賴notice() {this.effects.forEach(effect => {effect();});} } const effectWatch = (effect) => {currentEffect = effect;effect(); // 別忘了首先就會執(zhí)行一次currentEffect = null; }const dep = new Dep(10); let b; effectWatch(() => {b = dep.value + 10;console.log('effectWatch', b); }); dep.value = 20;為了講解時方便區(qū)分,所以我在實現(xiàn)effect時,重新取名叫effectWatch;不墨跡了,趕緊看代碼,相對于第一步,Dep類新增了兩個方法,分別是儲存依賴的函數(shù)和觸發(fā)依賴的函數(shù)。首先effectWatch在調(diào)用時就會執(zhí)行一次依賴函數(shù),并且是在effectWatch的參數(shù)中取dep.value值時,就會將當(dāng)前依賴函數(shù)儲存,當(dāng)我們對dep.value賦值時,會再次觸發(fā)依賴的函數(shù)。
到此為止我們只是實現(xiàn)了一個簡化單一的‘reactive’和effect,真正的reacive可不僅僅只是傳一個普通字符的功能,一起想想下一步該怎么做呢?
3.reactive實現(xiàn)
前面我們已經(jīng)實現(xiàn)了簡單地Dep,它只是一個單一的變量,遠(yuǎn)遠(yuǎn)不能滿足我們的開發(fā)需求。如果我們通過嵌套調(diào)用Dep,是不是就能實現(xiàn)reactive的功能了?而且開局我們已經(jīng)了解到需要使用到proxy對數(shù)據(jù)進(jìn)行劫持,那么是不是就好實現(xiàn)多了。
第一步
const reactive = (raw) => {return new Proxy(raw, {get(target, key) {console.log('get----', target[key]);return Reflect.get(target, key);},set(target, key, value) {console.log('set----', key, value);return Reflect.set(target, key, value);}}); } const user = reactive({name: '春賞百花冬觀雪', }); user.name; user.age = 24; user.age;我們先科普下proxy,就作者而言,很早就學(xué)習(xí)過該方法,但對于它的應(yīng)用確實少的可憐(唯唯諾諾的小菜雞)。proxy就是在我們訪問對象前添加了一層攔截,從而實現(xiàn)基本操作的攔截和自定義(我理解為過濾),而且proxy常常與Reflect成對出現(xiàn),Reflect也就是反射,它的出現(xiàn)簡化了我們調(diào)用_Object對象_的代碼,保持JS的簡單,它們之間的基情還需要讀者自行去了解哦。
對于user我們可以理解為一個較為復(fù)雜的對象,此時的它的每一個屬性key所對應(yīng)的值是不是相當(dāng)于我前面的dep呢,那么我們只需要再訪問該key值時將它通過Dep初始化,從而使整個user的任意屬性值發(fā)生變化,那么所依賴的函數(shù)也能再次執(zhí)行了,整個user也就成了響應(yīng)式對象了。從代碼來看,第一步依舊是 利用存取器對該user對象進(jìn)行監(jiān)聽。
第二步
const targetMap = new Map(); // 用于搜集經(jīng)過reactive初始化的變量 const getDep = (target, key) => {let depsMap = targetMap.get(target); // 從targetMap取,如果有的話if (!depsMap) { // 沒有就先儲存depsMap = new Map();targetMap.set(target, depsMap);}let dep = depsMap.get(key); // 并將dep與target的key建立連接if (!dep) {dep = new Dep();depsMap.set(key, dep);}return dep; }; const reactive = (raw) => {return new Proxy(raw, {get(target, key) {const dep = getDep(target, key);dep.depend(); // 當(dāng)我們訪問對象每個屬性時,都會收集依賴return Reflect.get(target, key);},set(target, key, value) {const dep = getDep(target, key);const result = Reflect.set(target, key, value); // 重新設(shè)置值之后dep.notice(); // 觸發(fā)依賴return result; // 再return}}); }; const user = reactive({name: '春賞百花冬觀雪', }); effectWatch(() => {user.name;console.log('effect---', user.name); }); user.name = '曉看天色暮觀云';這里要注意為什么接收Reflect.set后再return拋出,因為我們需要將user.name的值更新后,緊接著觸發(fā)我們收集到的依賴,最后才能拋出以完成set。
作者也是前端小白一枚,只是通過自己的學(xué)習(xí)并記錄以方便自己的回顧,也希望讀者大佬們能提出寶貴意見,共同進(jìn)步。
總結(jié)
以上是生活随笔為你收集整理的vue3解读—reactivity响应式实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ProgressBar.js – 漂亮的
- 下一篇: 响应式开发总结