利用Dectorator分模块存储Vuex状态

栏目: JavaScript · 发布时间: 5年前

内容简介:在H5的Vue项目中,最为常见的当为单页应用(SPA),利用设计模式中有一种装饰器模式,可以在运行时扩展对象的功能,而无需创建多个继承对象。类似的,Decorator可以在编译时扩展一个对象的功能,降低代码耦合度的同时实现多继承一样的效果。目前Decorator还只是一个提案,在生产环境中无法直接使用,可以用

在H5的Vue项目中,最为常见的当为单页应用(SPA),利用 Vue-Router 控制组件的挂载与复用,这时使用Vuex可以方便的维护数据状态而不必关心组件间的数据通信。但在Weex中,不同的页面之间使用不同的执行环境,无法共享数据,此时多为通过 BroadcastChannelstorage 模块来实现数据通信,本文主要使用修饰器(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本身可以用 subscribesubscribeAction 订阅相应的 mutationaction ,但只支持同步执行,而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 ,将 setItemgetItem 封装为 promise 对象,后续使用时可以避免过多的回调结构。

现在我们把 storagesetItem 方法写入到修饰器:

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 存储是异步的,为防止组件挂载以后发送请求返回的数据被本地数据覆盖,需要在本地数据读取并 mergestate 以后再调用 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 分模块读取数据再 mergestate ,提高数据存储效率的同时实现与业务逻辑代码的解耦。但还存在一些可优化的点:

1、触发action会将所有 module 中的所有 state 全部,只需保存所需状态,避免存储无用数据。

2、对于通过 registerModule 注册的 module ,需支持自动读取本地数据。

3、无法通过 _modulesNamespaceMap 获取 namespacedfalsemodule ,需改为遍历 _children

在此不再展开,将在后续版本中实现。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

解密硅谷

解密硅谷

[美]米歇尔 E. 梅西纳(Michelle E. Messina)、乔纳森 C. 贝尔(Jonathan C. Baer) / 李俊、李雪 / 机械工业出版社 / 2018-12 / 50.00

《解密硅谷》由身处硅谷最中心的连续创业者米歇尔·梅西纳和资深的投资人乔纳森·贝尔联合撰写,二人如庖丁解牛一般为读者深入剖析硅谷成功的原因:从硅谷的创新机制、创业生态、投资领域的潜规则、秘而不宣的价值观等角度,让阅读本书的人能够在最短的时间内,拥有像硅谷人一样的商业头脑,从而快速发现机遇,顺利地躲过创业的坑,熬过创业生死挑战中的劫数,带领初创公司顺利地活下去,并实现快速增长。 如果初创公司能够......一起来看看 《解密硅谷》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具