1、引言
在上篇中我們介紹了利用Decorator分模組來儲存資料,在Weex頁面例項建立時讀取資料到Vuex,但是存在無效資料儲存冗餘的問題,並且不支援registerModule
來動態存取資料,接下來主要介紹下利用Decorator來實現資料儲存的黑白名單和模組動態註冊時資料存取的實現方式。
2、 設定state
黑白名單
在設定state
的黑白名單時,我們需要對各個模組單獨設定,即允許在不同module
中設定黑或白名單,同時需要在unregisterModule
移除模組時同時移除黑白名單中的引用,在這裡我們使用WeakMap
和WeakSet
來儲存相關資訊,避免module
登出時無法釋放相應的prop
。
2.1、getter
的依賴收集
WeakMap
是ES6中新增的資料型別,其鍵屬於弱引用,可以很好的避免在沒有其他引用時,因Map
存在引用無法釋放而導致的記憶體洩漏。首先,需要建立黑白名單的修飾器:
const WHITE_TAG = 1; // 白名單tag
const BLACK_TAG = 2; // 黑名單tag
// 儲存state的資訊
const filterMap = new WeakMap();
// 儲存state[prop]的getter,用於在setState時過濾無效的prop
const descriptorSet = new WeakSet();
// 接受黑白名單的tag,返回對應的修飾器
const createDescriptorDecorator = tag => (target, prop) => {
if (!filterMap.has(tag)) {
// 設定當前state的黑白名單資訊
filterMap.set(target, tag);
} else if (filterMap.get(target) ^ tag) {
// 在同一state中,不能同時存在黑名單和白名單
throw new Error('...');
}
let value = target[prop];
return {
enumerable: true,
configurable: true,
get: function() {
const {get: getter} = Object.getOwnPropertyDescriptor(target, prop) || {};
if (getter && !descriptorSet.has(getter)) {
// 收集依賴到WeakSet
descriptorSet.add(getter);
}
return value;
},
set: function(newVal) {
value = newVal;
}
};
};
const shouldWrite = createDescriptorDecorator(WHITE_TAG);
const forbidWrite = createDescriptorDecorator(BLACK_TAG);
複製程式碼
這樣就完成了修飾器的編寫,使用時對需要的state
的property
修飾即可:
const module = {
state: () => ({
@shouldWrite
property1: {},
property2: [],
})
};
複製程式碼
此時當讀取state.property1
時就會將property1
的getter
存入到descriptorSet
中,供過濾時使用。
2.2、解析module
的state
在getter
觸發依賴收集後,我們將state
的property
存入了descriptorSet
,現在我們需要根據module
的state
,利用descriptorSet
將無效的資料過濾掉,返回pureState
:
/**
* [根據黑白名單,過濾state中無效的資料,返回新state]
* @param {[type]} module [state對應的module]
* @param {[type]} state [待過濾的state物件]
* @return {[type]} [新的state]
*/
const parseModuleState = (module, state) => {
const {_children, state: moduleState} = module;
const childrenKeys = Object.keys(_children);
// 獲取state中各個property的getter
const descriptors = Object.getOwnPropertyDescriptors(moduleState);
// 判斷當前state啟用黑名單還是白名單,預設黑名單
const tag = filterMap.get(moduleState) || BLACK_TAG;
const isWhiteTag = tag & WHITE_TAG;
// Object.fromEntries可以用object.fromentries來polyfill
const pureState = Object.fromEntries(Object.entries(state).filter(([stateKey]) => {
const {get: getter} = descriptors[stateKey] || {};
// 過濾子模組的資料,只保留白名單或不在黑名單裡的資料
return !childrenKeys.some(childKey => childKey === stateKey)
&& !((isWhiteTag ^ descriptorSet.has(getter)));
}));
return pureState;
}
複製程式碼
這樣剔除了冗餘的資料,只保留有效資料存入storage
。
3、registerModule
Vuex支援registerModule
和unregisterModule
來動態的註冊模組和登出模組,並且可以通過設定{preserveState: true}
來儲存SSR
渲染的資料,在Weex中同樣需要在registerModule
時初始化已有的資料到state
,這裡我們選擇在外掛初始化時擴充套件下:
// 根據傳入的newState,初始化module及其子module的state
const setModuleState = async (store, path, newState) => {
const namespace = path.length ? normalizeNamespace(path) : '';
const module = store._modulesNamespaceMap[namespace];
const setChildModuleState = function setChildModuleState(_module, _state) {
const {_children, state} = _module;
const childrenKeys = Object.keys(_children);
Object.entries(_state).map(([key, value]) => {
if (childrenKeys.every(a => a !== key)) {
state[key] = value;
} else if (_children[key]) {
// 子模組遞迴設定
setChildModuleState(_children[key], _state[key]);
}
});
};
setChildModuleState(module, newState);
};
export const createStatePlugin = (option = {}) => {
const {supportRegister = false} = option;
return function(store) {
if (supportRegister) {
// 擴充套件registerModule方法,先保留對原方法的引用
const registerModule = store.registerModule;
// 涉及到對storage的操作,將其擴充套件為async方法
store.registerModule = async function(path, rawModule, options) {
// 呼叫Vuex的registerModule
registerModule.call(store, path, rawModule, options);
// 在註冊module完成後,初始化資料
const {rawState} = options || {};
const newState = typeof rawState === 'function' ? rawState() : rawState;
if (newState) {
// 設定state
setModuleState(store, path, newState);
...
}
}
}
}
}
複製程式碼
以上就完成了在registerModule
時,對state
資料初始化操作,還可以呼叫parseModuleState
後模組所需資料存入storage
,unregisterModule
的擴充套件更簡單,只需在登出的同時刪除storage
中對應模組的資料即可,再次不再贅述。
4、小結
在Weex開發時,因各個頁面例項擁有獨立的JS執行環境,無法共享Vuex資料,所以在專案中會使用storage
、BroadcastChannel
等來實現頁面通訊和資料共享。本文利用Decorator來實現Vuex資料的分模組儲存,同時提供state
的property
黑白名單過濾及registerModule
時資料動態初始化功能。以上是本人專案中的一點心得,如若有誤,還請斧正。