冒險解密遊戲《勇者鬥么蛾》發售 支援簡體中文
阿新 • • 發佈:2022-04-02
目錄
響應性概念
響應性:當狀態更新,系統會自動更新關聯狀態;在Web場景下,指的是不斷變化的狀態反映到DOM上的變化。
例如實現一個功能,使得變數b
的值總是變數a
的值的10倍。如果我們擁有一個magic function onAChanged()
,即當a
的值改變之後,自動呼叫該函式,則可以實現類似的功能。
// 當a的值改變, 呼叫該回調函式 onAChanged(() => { document.querySelector('.cell.b1').textContent = a * 10; // 上面的程式碼可以抽象為: // view = render(state); // 當狀態改變, 渲染對應的DOM元素 });
在React中,實現方式類似:
let update;
const onStateChanged = _update => {
update = _update;
};
// 必須使用setState更新狀態
const setState = newState => {
state = newState;
update();
};
在Angular中,使用髒值檢測實現,攔截例如點選等時間,檢查資料是否被更新。
在Vue中,使用ES5的Object.defineProperty()
方法,重寫物件所有屬性的getter
和setter
方法。
getter和setter
通過ES5的Object.defineProperty()
方法,監聽屬性值的變更,注意:
- 下面的做法相當於,redefine物件
obj
的key
屬性,所以要求configurable
不能為false
- 所以,當再次呼叫
convert(stu)
時,會報錯
function convert(obj) { // 通過forEach監聽obj物件的每個屬性 Object.keys(obj).forEach(key => { let internalValue = obj[key]; // 通過閉包儲存原來的值 Object.defineProperty(obj, key, { configurable: false, // 該屬性不能被redefine get() { console.log(`getting key "${key}": ${internalValue}`); return internalValue; }, set(newValue) { console.log(`setting key "${key}" to: ${newValue}`); internalValue = newValue; } }); }); } let stu = { name: 'Lee', age: 20 }; convert(stu); console.log(stu); // { name: [Getter/Setter], age: [Getter/Setter] } let age = stu.age; // getting key "age": 20 stu.age = 50; // setting key "age" to: 50 // 由於上面已經將新的屬性值設定為configurable: false, 所以不能進行redefine // convert(stu); // TypeError: Cannot redefine property: name
可以看到,當使用convert()
方法轉換stu
物件之後,每當讀取/修改物件的屬性時,都會收到提醒。
依賴追蹤
我們期望實現一個Dep
類,它可以使用depend()
方法收集依賴項,當所依賴項發生改變時,使用notify()
方法觸發依賴項的執行。
const dep = new Dep();
// 自動執行, 收集依賴項
autorun(() => {
dep.depend(); // 收集依賴項
console.log('updated');
})
dep.notify(); // 通知以上收集的依賴項: 所依賴的變數updated
實現一個真正的Dep
類:
let activeEffect; // 當前受依賴項影響的函式
class Dep {
subscribers = new Set(); // 所有受依賴項影響的函式
depend() { // 收集當前受依賴項影響的函式, 加入subscribers
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() { // 通知所有受依賴項影響的函式: 依賴項已經被改變, 需要執行函式
this.subscribers.forEach(effect => effect());
}
}
// 建立起effect函式與其依賴項的訂閱關係: 當依賴項被改變, 執行effect()
function watchEffect(effect) {
activeEffect = effect;
effect();
}
如果我們要使用Dep
類,很明顯,依賴項不能是普通的物件,而是需要設定過getter和setter的物件,該物件屬性的getter和setter需要完成的額外功能是:
-
get()
:當值被讀取時,使該值對應的dep
收集依賴 -
set()
:當值被修改時,通知受該值依賴的函式執行
例如,我們實現上面提到的功能,變數b
的值總是變數a
的值的10倍:
/* use Dep */
const dep = new Dep();
let a = 0, b = 0;
const state = {}; // 以後需要使用state操作a, 從而實現對a的資料劫持
Object.defineProperty(state, 'a', {
get() {
dep.depend();
return a;
},
set(val) {
if (a !== val) {
a = val;
dep.notify();
}
}
});
// effect: () => {b = state.a * 10;}
// 首先在watchEffect函式中, 由於執行了effect(), 所以對state.a進行了讀取, dep.depend()新增訂閱
// 於是, 每當state.a的值改變, dep.notify()執行受state.a依賴的effect()
watchEffect(() => {
b = state.a * 10;
});
console.log(state.a); // 0
console.log(b); // 0
state.a = 10;
console.log(b); // 100
迷你觀察者
我們將上面的convert()
函式和Dep
類進行結合,就得到了一個迷你觀察者:
function observe(raw) {
// 1. 遍歷物件的所有key
Object.keys(raw).forEach(key => {
// 2. 為每個key建立一個dep物件
const dep = new Dep();
// 3. 重寫物件的key屬性
let realVal = raw[key];
Object.defineProperty(raw, key, {
get() {
dep.depend(); // 4. 讀取key屬性時, 建立依賴
return realVal;
},
set(newVal) {
realVal = newVal;
dep.notify(); // 4. key屬性改變時, 通知被依賴的effect
}
});
});
return raw;
}
我們再實現上面的功能,就會更加簡潔,不用手動重寫getter和setter了:
let obj = {a: 1}; // 依賴項a
let b = 0;
observe(obj);
watchEffect(() => {
b = obj.a * 10;
});
console.log(b); // 10 (這是由於在watchEffect()中以及執行過effect()一次了)
obj.a = 20;
console.log(b); // 200