利用Dectorator分模块存储Vuex状态
栏目: JavaScript · 发布时间: 6年前
内容简介:在H5的Vue项目中,最为常见的当为单页应用(SPA),利用设计模式中有一种装饰器模式,可以在运行时扩展对象的功能,而无需创建多个继承对象。类似的,Decorator可以在编译时扩展一个对象的功能,降低代码耦合度的同时实现多继承一样的效果。目前Decorator还只是一个提案,在生产环境中无法直接使用,可以用
在H5的Vue项目中,最为常见的当为单页应用(SPA),利用 Vue-Router
控制组件的挂载与复用,这时使用Vuex可以方便的维护数据状态而不必关心组件间的数据通信。但在Weex中,不同的页面之间使用不同的执行环境,无法共享数据,此时多为通过 BroadcastChannel
或 storage
模块来实现数据通信,本文主要使用修饰器(Decorator)来扩展Vuex的功能,实现分模块存储数据,并降低与业务代码的耦合度。
2、Decorator
设计模式中有一种装饰器模式,可以在运行时扩展对象的功能,而无需创建多个继承对象。类似的,Decorator可以在编译时扩展一个对象的功能,降低代码耦合度的同时实现多继承一样的效果。
2.1、Decorator安装
目前Decorator还只是一个提案,在生产环境中无法直接使用,可以用 babel-plugin-transform-decorators-legacy
来实现。使用npm管理依赖包的可以执行以下命令:
npm install babel-plugin-transform-decorators-legacy -D 复制代码
然后在 .babelrc 中配置
{
"plugins": [
"transform-decorators-legacy"
]
}
复制代码
或者在 webpack.config.js 中配置
{
test: /\.js$/,
loader: "babel-loader",
options: [
plugins: [
require("babel-plugin-transform-decorators-legacy").default
]
]
}
复制代码
这时可以在代码里编写Decorator函数了。
2.2、Decorator的编写
在本文中,Decorator主要是对方法进行修饰,主要代码如下:
decorator.js
const actionDecorator = (target, name, descriptor) => {
const fn = descriptor.value;
descriptor.value = function(...args) {
console.log('调用了修饰器的方法');
return fn.apply(this, args);
};
return descriptor;
};
复制代码
store.js
const module = {
state: () => ({}),
actions: {
@actionDecorator
someAction() {/** 业务代码 **/ },
},
};
复制代码
可以看到, actionDecorator
修饰器的三个入参和 Object.defineProperty
一样,通过对 module.actions.someAction
函数的修饰,实现在编译时重写 someAction
方法,在调用方法时,会先执行 console.log('调用了修饰器的方法');
,而后再调用方法里的业务代码。对于多个功能的实现,比如存储数据,发送广播,打印日志和数据埋点,增加多个Decorator即可。
3、Vuex
Vuex本身可以用 subscribe
和 subscribeAction
订阅相应的 mutation
和 action
,但只支持同步执行,而Weex的 storage
存储是异步操作,因此需要对Vuex的现有方法进行扩展,以满足相应的需求。
3.1、修饰action
在Vuex里,可以通过 commit mutation
或者 dispatch action
来更改 state
,而 action
本质是调用 commit mutation
。因为 storage
包含异步操作,在不破坏Vuex代码规范的前提下,我们选择修饰action来扩展功能。
storage
使用回调函数来读写 item
,首先我们将其封装成 Promise
结构:
storage.js
const storage = weex.requireModule('storage');
const handler = {
get: function(target, prop) {
const fn = target[prop];
// 这里只需要用到这两个方法
if ([
'getItem',
'setItem'
].some(method => method === prop)) {
return function(...args) {
// 去掉回调函数,返回promise
const [callback] = args.slice(-1);
const innerArgs = typeof callback === 'function' ? args.slice(0, -1) : args;
return new Promise((resolve, reject) => {
fn.call(target, ...innerArgs, ({result, data}) => {
if (result === 'success') {
return resolve(data);
}
// 防止module无保存state而出现报错
return resolve(result);
})
})
}
}
return fn;
},
};
export default new Proxy(storage, handler);
复制代码
通过 Proxy
,将 setItem
和 getItem
封装为 promise
对象,后续使用时可以避免过多的回调结构。
现在我们把 storage
的 setItem
方法写入到修饰器:
decorator.js
import storage from './storage';
// 加个rootKey,防止rootState的namespace为''而导致报错
// 可自行替换为其他字符串
import {rootKey} from './constant';
const setState = (target, name, descriptor) => {
const fn = descriptor.value;
descriptor.value = function(...args) {
const [{state, commit}] = args;
// action为异步操作,返回promise,
// 且需在状态修改为fulfilled时再将state存储到storage
return fn.apply(this, args).then(async data => {
// 获取store的moduleMap
const rawModule = Object.entries(this._modulesNamespaceMap);
// 根据当前的commit,查找此action所在的module
const moduleMap = rawModule.find(([, module]) => {
return module.context.commit === commit;
});
if (moduleMap) {
const [key, {_children}] = moduleMap;
const childrenKeys = Object.keys(_children);
// 只获取当前module的state,childModule的state交由其存储,按module存储数据,避免存储数据过大
// Object.fromEntries可使用object.fromentries来polyfill,或可用reduce替代
const pureState = Object.fromEntries(Object.entries(state).filter(([stateKey]) => {
return !childrenKeys.some(childKey => childKey === stateKey);
}));
await storage.setItem(rootKey + key, JSON.stringify(pureState));
}
// 将data沿着promise链向后传递
return data;
});
};
return descriptor;
};
export default setState;
复制代码
完成了 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('/')}/`)) || {};
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
。
在此不再展开,将在后续版本中实现。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 如何使用Fedora模块化存储库
- 蚂蚁金服生产级 Raft 算法库存储模块剖析 | SOFAJRaft 实现原理 原 荐
- 块存储、文件存储、对象存储三者之比较
- Node.js模块系统 (创建模块与加载模块)
- 黑客基础,Metasploit模块简介,渗透攻击模块、攻击载荷模块
- 云原生存储详解:容器存储与 K8s 存储卷
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Effective STL中文版
[美] Scott Meyers / 潘爱民、陈铭、邹开红 / 电子工业出版社 / 2013-5 / 59.00元
《Effective STL中文版:50条有效使用STL的经验》是EffectiveC++的第3卷,被评为“值得所有C++程序员阅读的C++书籍之一”。《Effective STL中文版:50条有效使用STL的经验》详细讲述了使用STL的50条指导原则,并提供了透彻的分析和深刻的实例,实用性极强,是C++程序员必备的基础书籍。C++的标准模板库(STL)是革命性的,要用好STL并不容易。《Effe......一起来看看 《Effective STL中文版》 这本书的介绍吧!