魚滑怎么制作教程搜索引擎優(yōu)化seo的英文全稱是
通過上一篇文章已經(jīng)初始化項目,集成了ts
和jest
。本篇實現(xiàn)Vue3中響應式模塊里的reactive
方法。
前置知識要求
如果你熟練掌握Map, Set, Proxy, Reflect,可直接跳過這部分。
Map
Map是一種用于存儲鍵值對的集合,并且能夠記住鍵的原始插入順序。 其中鍵和值可以是任意類型的數(shù)據(jù)。
初始化,添加,獲取
let myMap = new Map()myMap.set('name', 'wendZzoo')
myMap.set('age', 18)myMap.get('name')
myMap.get('age')
Map 中的一個鍵只能出現(xiàn)一次,它在 Map 的集合中是獨一無二的,重復設置的會被覆蓋
myMap.set('name', 'jack')
Map 的鍵和值可以是任意類型的數(shù)據(jù)
myMap.set({name: 'wendZzoo'}, [{age: 18}])
刪除
let myMap = new Map()
myMap.set('name', 'Tom')
myMap.delete('name')
key
數(shù)據(jù)類型是對象時,需要使用對應的引用來刪除鍵值對
let myMap = new Map()
let key = [{name: 'Tom'}]
myMap.set(key, 'Hello')
myMap.delete(key)// 如果使用不同的引用來嘗試刪除鍵值對
// 它將無法正常工作
// 因為Map無法識別這兩個引用是相同的鍵
myMap.set([{name: 'Tom'}], 'Hello')
myMap.delete([{name: 'Tom'}])
Set
Set是一種集合數(shù)據(jù)結(jié)構(gòu),它允許存儲唯一的值,無重復項。Set對象可以存儲任何類型的值,包括基本類型和對象引用。
let mySet = new Set()mySet.add('wendZzoo')
mySet.add(18)
mySet.add({province: 'jiangsu', city: 'suzhou'})
可迭代
for (let key of mySet) {console.log(key)
}
Proxy
Proxy 對象用于創(chuàng)建一個對象的代理,從而實現(xiàn)基本操作的攔截和自定義(如屬性查找、賦值、枚舉、函數(shù)調(diào)用等)。
Vue 響應式的前提就是需要數(shù)據(jù)劫持,在 JS 中有兩種劫持 property 訪問的方式:getter / setters 和 Proxies。Vue 2 使用 getter / setters 完全是出于支持舊版本瀏覽器的限制,而在 Vue 3 中則使用了 Proxy 來創(chuàng)建響應式對象。
創(chuàng)建 Proxy 對象時,需要提供兩個參數(shù):目標對象 target(被代理的對象)和一個處理程序?qū)ο?handler(用于定義攔截行為的方法)。
其中 handler 常用的有 get,set 方法。
handler.get() 方法用于攔截對象的讀取屬性操作,完整使用可以參考:MDN
它接收三個參數(shù):
- target:目標對象
- property:被獲取的屬性名
- receiver:Proxy 或者繼承 Proxy 的對象
const obj = {name: 'wendZzoo', age: 18}
let myProxy = new Proxy(obj, {get: (target, property, receiver) => {console.log('收集依賴')return target[property]}
})// 執(zhí)行 myProxy.name
// 執(zhí)行 myProxy.age
handler.set() 方法是設置屬性值操作的捕獲器,完整使用參考:MDN
它接收四個參數(shù)
- target:目標對象
- property:將被設置的屬性名或 Symbol、
- value:新屬性值
- receiver:最初被調(diào)用的對象。通常是 proxy 本身,但 handler 的 set 方法也有可能在原型鏈上,或以其他方式被間接地調(diào)用(因此不一定是 proxy 本身)
const obj = {name: 'wendZzoo', age: 18}
let myProxy = new Proxy(obj, {get: (target, property, receiver) => {console.log('收集依賴')return target[property]},set: (target, property, value, receiver) => {console.log('觸發(fā)依賴')target[property] = valuereturn true}
})
myProxy.name = 'Jack'
myProxy.age = 20
Proxy 提供了一種機制,通過攔截和修改目標對象的操作來實現(xiàn)自定義行為,在 get 和 set 方法打印日志的地方,也就是 Vue3 實現(xiàn)依賴收集和觸發(fā)依賴的地方。
Reflect
Reflect 是一個內(nèi)置的對象,它提供攔截 JS 操作的方法。這讓它可以完美的和 Proxy 配合,Proxy 提供了對對象攔截的時機位置,Reflect 提供攔截方法。
Reflect 不是一個構(gòu)造函數(shù),因此不能 new 進行調(diào)用,更像 Math 對象,作為一個函數(shù)來調(diào)用,它所有的屬性和方法都是靜態(tài)的。
常用的方法有 get,set。
Reflect.get方法允許你從一個對象中取屬性值,完整使用參考:MDN
它接收三個參數(shù):
- target:需要取值的目標對象
- propertyKey:需要獲取的值的鍵值
- receiver:如果 target 對象中指定了getter,receiver 則為 getter 調(diào)用時的this值
let obj = {name: 'wendZzoo', age: 18}
Reflect.get(obj, 'name')
Reflect.get(obj, 'age')
Reflect.set 方法允許在對象上設置屬性,完整使用參考:MDN
它接收三個參數(shù):
- target:設置屬性的目標對象
- propertyKey:設置的屬性的名稱
- value:設置的值
- receiver:如果遇到 setter,receiver則為setter調(diào)用時的this值
let obj = {}
Reflect.set(obj, 'name', 'wendZzoo')let arr = ['name', 'address']
Reflect.set(arr, 1, 'age')
Reflect.set(arr, 'length', 1)
更改目錄
src
下新建文件夾reactivity
,新建effect.ts
和reactive.ts
。
tests
文件夾下刪除上一篇文章中用于驗證jest
安裝的index.spec.ts
,新建effect.spec.ts
和reactive.spec.ts
。
reactive
先寫單測,明確需要的成果,再根據(jù)這個需求來實現(xiàn)函數(shù)。Vue3的reactive
方法返回一個對象的響應式代理,那代理的對象和源對象是不同的,但是又能和源對象一樣的嵌套結(jié)構(gòu)。
那單測可以這樣寫:reactive.spec.ts
import { reactive } from "../reactivity/reactive";describe("reactive", () => {it("happy path", () => {let original = { foo: 1 };let data = reactive(original);expect(data).not.toBe(original);expect(data.foo).toBe(1);});
});
根據(jù)這兩個斷言,來實現(xiàn)現(xiàn)階段的reactive
方法。Vue3中是使用Proxy實現(xiàn)。
reactive.ts
export function reactive(raw) {return new Proxy(raw, {get: (target, key) => {let res = Reflect.get(target, key);// TODO 依賴收集return res;},set: (target, key, value) => {let res = Reflect.set(target, key, value);// TODO 觸發(fā)依賴return res;},});
}
運行reactive
單測,來驗證該方法實現(xiàn)是否正確,執(zhí)行yarn test reactive
effect
在官網(wǎng)上是沒有單獨提到這個 API 的,可以在進階主題的深入響應式系統(tǒng)一篇中找到它的身影。
effect
直接翻譯為作用,意思是使其發(fā)生作用,這個使其的其就是我們傳入的函數(shù),所以effect
的作用就是讓我們傳入的函數(shù)發(fā)生作用,也就是執(zhí)行這個函數(shù)。
使用示例
import { reactive, effect } from "vue";let user = reactive({age: 10,
});let nextAge;function setAge() {effect(() => {nextAge = user.age + 1;});console.log(nextAge);
}function updateAge() {user.age++;console.log(nextAge);
}
在沒有使用effect
作用于nextAge
時,直接觸發(fā)updateAge
方法,輸出的nextAge
就是undefined
;
調(diào)用setAge
,effect
中函數(shù)執(zhí)行給nextAge
賦值,響應式數(shù)據(jù)user
中age
變化,nextAge
也在繼續(xù)執(zhí)行effect
中函數(shù)。
單測
那effect
的單測可以寫成這樣:
import { effect } from "../reactivity/effect";
import { reactive } from "../reactivity/reactive";describe("effect", () => {it("happy path", () => {let user = reactive({age: 10,});let nextAge;effect(() => {nextAge = user.age + 1;});expect(nextAge).toBe(11);});
});
effect
方法就是接收一個方法,并執(zhí)行它。
effect.ts
class ReactiveEffect {private _fn: any;constructor(fn) {this._fn = fn;}run() {this._fn();}
}export function effect(fn) {let _effect = new ReactiveEffect(fn);_effect.run();
}
通過抽離成一個Class
類,去執(zhí)行傳入的 fn
參數(shù)。
再來執(zhí)行所有的單測,驗證是否成功,執(zhí)行yarn test
依賴收集
修改effect
單測,增加一個斷言,來判斷當age
變化時,nextAge
是否也更新了?
import { effect } from "../reactivity/effect";
import { reactive } from "../reactivity/reactive";describe("effect", () => {it("happy path", () => {let user = reactive({age: 10,});let nextAge;effect(() => {nextAge = user.age + 1;});expect(nextAge).toBe(11);// +++updateruser.age++;expect(nextAge).toBe(12);});
});
執(zhí)行單測發(fā)現(xiàn)無法通過,是因為Proxy
代理時候并沒有實現(xiàn)依賴收集和觸發(fā)依賴,也就是reactive.ts
中還有兩個 TODO。
但是,首先得清楚什么叫依賴?
引用官方的例子:
let A0 = 1
let A1 = 2
let A2 = A0 + A1console.log(A2) // 3A0 = 2
console.log(A2) // 仍然是 3
當我們更改 A0 后,A2 不會自動更新。
那么我們?nèi)绾卧?JavaScript 中做到這一點呢?首先,為了能重新運行計算的代碼來更新 A2,我們需要將其包裝為一個函數(shù):
let A2function update() {A2 = A0 + A1
}
然后,我們需要定義幾個術語:
- 這個 update() 函數(shù)會產(chǎn)生一個副作用,或者就簡稱為作用 (effect),因為它會更改程序里的狀態(tài)。
- A0 和 A1 被視為這個作用的依賴 (dependency),因為它們的值被用來執(zhí)行這個作用。因此這次作用也可以說是一個它依賴的訂閱者 (subscriber)。
因此我們可以大膽通俗的講,依賴就是指的是觀察者(通常是視圖或副作用函數(shù))對數(shù)據(jù)的依賴關系。當觀察者需要訪問特定數(shù)據(jù)時,它就成為該數(shù)據(jù)的依賴。
那依賴收集呢?
依賴收集是用于追蹤和管理數(shù)據(jù)依賴關系。常用于實現(xiàn)響應式系統(tǒng),其中數(shù)據(jù)的變化會自動觸發(fā)相關的更新操作。
當數(shù)據(jù)發(fā)生改變時,相關的視圖或操作也能夠自動更新,以保持數(shù)據(jù)和界面的同步。依賴收集可以幫助我們建立起數(shù)據(jù)和視圖之間的關聯(lián),確保數(shù)據(jù)的變化能夠自動反映在視圖上。
從代碼層面講,讀取對象的時候也就是get操作時,進行依賴收集,將目標對象target,對象中key,Dep實例做關聯(lián)映射。
在effect.ts
中定義依賴收集的方法track
。
class ReactiveEffect {private _fn: any;constructor(fn) {this._fn = fn;}run() {reactiveEffect = this;this._fn();}
}let targetMap = new Map();
export function track(target, key) {// target -> key -> deplet depMap = targetMap.get(target);if (!depMap) { // initdepMap = new Map();targetMap.set(target, depMap);}let dep = depMap.get(key);if (!dep) { // initdep = new Set();depMap.set(key, dep);}dep.add(reactiveEffect);
}let reactiveEffect;
export function effect(fn) {let _effect = new ReactiveEffect(fn);_effect.run();
}
觸發(fā)依賴
在設置對象屬性時,也就是進行set
操作時,觸發(fā)依賴。將每個屬性上掛載的dep
的Set
結(jié)構(gòu)中的所有作用函數(shù)執(zhí)行。
export function trigger(target, key) {let depMap = targetMap.get(target);let dep = depMap.get(key);for (const effect of dep) {effect.run();}
}
至此,再次執(zhí)行所有單測,yarn test
總結(jié)
- 先通過單測入手,明確需要實現(xiàn)的函數(shù)方法的功能
- 分布實現(xiàn)功能點,即拆分功能點,先初步實現(xiàn)了
reactive
方法簡單版,只要求原數(shù)據(jù)和代理之后的數(shù)據(jù)不同,但是數(shù)據(jù)結(jié)構(gòu)又要一樣,像深拷貝一樣。 - 通過
Class
類,實現(xiàn)effect
方法可以自執(zhí)行其傳入的函數(shù)參數(shù) - 依賴收集,通過兩個
Map
結(jié)構(gòu)和一個Set
結(jié)構(gòu)來映射數(shù)據(jù)關系,將所有的fn
存放到dep
中。通過一個全局變量reactiveEffect
來獲取到effct
實例,為后續(xù)觸發(fā)依賴時,直接拿dep
中每一項去執(zhí)行。 - 觸發(fā)依賴,通過映射關系獲取到
dep
,因為dep
是Set
結(jié)構(gòu),可迭代,循環(huán)每項執(zhí)行。