完成了setState修饰器功能以后,就可以装饰action方法了,这样等action返回的promise状态修改为fulfilled后调用storage的存储功能,及时保存数据状态以便在新开Weex页面加载最新数据。
store.js
import setState from './decorator'; const module = { state: () => ({}), actions: { @setState someAction() {/** 业务代码 **/ }, }, };
3.2、读取module数据
完成了存储数据到storage以后,我们还需要在新开的Weex页面实例能自动读取数据并初始化Vuex的状态。在这里,我们使用Vuex的plugins设置来完成这个功能。
首先我们先编写Vuex的plugin:
plugin.js
import storage from './storage'; import {rootKey} from './constant'; const parseJSON = (str) => { try { return str ? JSON.parse(str) : undefined; } catch(e) {} return undefined; }; const getState = (store) => { const getStateData = async function getModuleState(module, path = []) { const {_children} = module; // 根据path读取当前module下存储在storage里的数据 const data = parseJSON(await storage.getItem(`${path.join('https://www.jb51.net/')}/`)) || {}; const children = Object.entries(_children); if (!children.length) { return data; } // 剔除childModule的数据,递归读取 const childModules = await Promise.all( children.map(async ([childKey, child]) => { return [childKey, await getModuleState(child, path.concat(childKey))]; }) ); return { ...data, ...Object.fromEntries(childModules), } }; // 读取本地数据,merge到Vuex的state const init = getStateData(store._modules.root, [rootKey]).then(savedState => { store.replaceState(merge(store.state, savedState, { arrayMerge: function (store, saved) { return saved }, clone: false, })); }); }; export default getState;
以上就完成了Vuex的数据按照module读取,但Weex的IOS/Andriod中的storage存储是异步的,为防止组件挂载以后发送请求返回的数据被本地数据覆盖,需要在本地数据读取并merge到state以后再调用new Vue,这里我们使用一个简易的interceptor来拦截:
interceptor.js
const interceptors = {}; export const registerInterceptor = (type, fn) => { const interceptor = interceptors[type] || (interceptors[type] = []); interceptor.push(fn); }; export const runInterceptor = async (type) => { const task = interceptors[type] || []; return Promise.all(task); };
这样plugin.js中的getState就修改为:
import {registerInterceptor} from './interceptor'; const getState = (store) => { /** other code **/ const init = getStateData(store._modules.root, []).then(savedState => { store.replaceState(merge(store.state, savedState, { arrayMerge: function (store, saved) { return saved }, clone: false, })); }); // 将promise放入拦截器 registerInterceptor('start', init); };
store.js
import getState from './plugin'; import setState from './decorator'; const rootModule = { state: {}, actions: { @setState someAction() {/** 业务代码 **/ }, }, plugins: [getState], modules: { /** children module**/ } };
app.js
import {runInterceptor} from './interceptor'; // 待拦截器内所有promise返回resolved后再实例化Vue根组件 // 也可以用Vue-Router的全局守卫来完成 runInterceptor('start').then(() => { new Vue({/** other code **/}); });
这样就实现了Weex页面实例化后,先读取storage数据到Vuex的state,再实例化各个Vue的组件,更新各自的module状态。
4、TODO
通过Decorator实现了Vuex的数据分模块存储到storage,并在Store实例化时通过plugin分模块读取数据再merge到state,提高数据存储效率的同时实现与业务逻辑代码的解耦。但还存在一些可优化的点:
1、触发action会将所有module中的所有state全部,只需保存所需状态,避免存储无用数据。
2、对于通过registerModule注册的module,需支持自动读取本地数据。
3、无法通过_modulesNamespaceMap获取namespaced为false的module,需改为遍历_children。
在此不再展开,将在后续版本中实现。