乐趣区

关于小程序:使用MpKit的事件MixinSetData优化全局拦截等功能增强开发多平台小程序

MpKit 与本文章均在不断更新,为保障您获取到的内容是最新的,请关注:https://imingyu.github.io/2020/mpkit/

前言

近年来,多个公司都开发出了小程序这样的“微利用”计划,其在生态扩大、性能加强等方面都施展着重要的作用;

作为开发者却要同时面对多个小程序平台,实现类似的性能,如果不思考应用框架的话,在底层的一些根底技术解决方案上,有没有一个轻量级的抉择?

咱们在开发一个小程序时,往往会有以下技术需要:

  1. 全局事件总线
  2. 优化小程序的 setData 函数
  3. 心愿将 App/Page/Component 上的共有性能拆分,并能够有效性的反复利用,甚至组建为 App/Page/Component 基类
  4. 能够全局拦挡执行某些操作,如:异样报错、网络申请、性能禁止、App/Page/Component 的生命周期等

基于以上需要,都是我以往实现过的性能,所以我将我的经验总结成了一个开源我的项目 MpKit,外面就蕴含对以上需要的性能实现,且不止于此;

MpKit 的次要性能都通过单元测试,可放心使用,我的项目主页:https://github.com/imingyu/mpkit;

上面我来介绍下它的具体用法和性能列表。

简介

MpKit 公布到了 NPM 平台,以模块化的形式划分为多个包,某些包不止能用在小程序平台,还能够用到 h5 等平台;某些包却无奈在小程序上应用,包列表及相干性能如下:

以下包均反对 TypeScript 语言

实用平台 简介
小程序 H5 Node.js
微信 支付宝 百度 字节跳动
@mpkit/inject 提供小程序环境实用的多种实用函数或组件,如 setData 优化、Mixin、事件总线等。查看文档
@mpkit/ebus 提供事件触发、监听等性能。查看文档
@mpkit/mixin 为小程序提供混入性能。查看文档
@mpkit/util 工具函数查看文档

装置

如果你的小程序我的项目中反对引入 npm 包,那么你间接依据本人的须要装置对应包即可,如:

npm i @mpkit/ebus -S

然而如果你是原生开发,不反对引入 npm 包,你最大的可能须要用到 @mpkit/inject 包,此包中的性能根本蕴含了其余包的所有性能,且反对按需插件化装置,可节俭你的字节空间;应用@mpkit/inject

  1. 在磁盘任意地位创立长期目录如xx/temp;
  2. 在此目录中执行命令:
npm i @mpkit/inject
  1. 找到 xx/temp/node_modules/@mpkit/inject 下的 dist 目录,将其下内容拷贝到你的小程序我的项目目录下;
  2. 找到 dist/config.js 文件,将你须要的性能插件引入,如:
// 提供事件类性能
import {plugin as EbusPlugin} from './plugins/ebus';
// 提供混入性能
import {plugin as MixinPlugin} from './plugins/mixin';
// 提供 setData 优化
import {plugin as SetDataPlugin} from './plugins/set-data';
var config = {
    // 是否重写全局变量
    rewrite: {
        App: false, // 重写全局变量 App 为 plugins/mixin 中的 MkApp 即 MpKit.App
        Page: false, // 重写全局变量 Page 为 plugins/mixin 中的 MkPage 即 MpKit.Page
        Component: false, // 重写全局变量 Component 为 plugins/mixin 中的 MkComponent 即 MpKit.Component
        Api: false, // 重写全局 api 变量 wx/my/tt 为 plugins/mixin 中的 MkApi 即 MpKit.Api
        setData: false // 重写 Page/Component 中的 setData 为优化后的 setData 即 MpKit.setData
    },
    plugins: [
        EbusPlugin,
        MixinPlugin,
        SetDataPlugin
    ]
};
export default config;
  1. 如果配置了 config.rewrite 项,请在小程序我的项目的 app.js 的第一行处引入 @mpkit/inject/dist 中的 index.js 文件,否则无奈实现全局变量重写;如果没有配置该项,则须要在应用时引入,如:
import MpKit from '@mpkit/inject/dist/index';
MpKit.on(...);
  1. 装置实现。
  2. 小提示:不须要的插件 js 文件能够间接删掉,插件不间接相互依赖。

应用

这里着重介绍 @mpkit/inject 包的应用形式和细节,其余包可自行参考对应文档;

@mpkit/inject包提供下列性能,

办法 / 变量 作用 依赖插件
on(eventName:string, handler:Function) 为某全局事件增加监听函数 ebus
off(eventName:string, handler:Function) 为某全局事件移除监听函数 ebus
emit(eventName:string, data:any) 触发某全局事件并传递数据 ebus
App(...mixins:MpAppSpec[]) : MpAppSpec 接管多个对象,对象构造与小程序 App 函数接管的对象构造统一,并将这些对象合并,返回一个新对象,蕴含所有对象的性能和数据,合并策略下文形容。 mixin
Page(...mixins:MpPageSpec[]) : MpPageSpec 接管多个对象,对象构造与小程序 Page 函数接管的对象构造统一,并将这些对象合并,返回一个新对象,蕴含所有对象的性能和数据,合并策略下文形容。 mixin
Component(...mixins:MpComponentSpec[]) : MpComponentSpec 接管多个对象,对象构造与小程序 Component 函数接管的对象构造统一,并将这些对象合并,返回一个新对象,蕴含所有对象的性能和数据,合并策略下文形容。 mixin
Api 与小程序上的 wx my swan tt 等对象的属性和办法统一,只不过在其办法上增加了钩子函数,不便拦挡解决 mixin
MixinStore.addHook(type:string, hook:MpMethodHook) MpKit中内置了很多全局钩子函数,方可实现了全局拦挡,setData 重写等性能,而如果你也想在全局增加本人的钩子函数,那么能够调用此函数 mixin
setData(view:any, data:any, callback:Function) : Promise<DiffDataResult> 以优化的形式向某个 Page/Component 设置数据,仅设置变动的数据,并以 Promise 的形式返回 diff 后的数据后果 set-data

依赖

从下面的性能列表中能够看到,某些办法或变量是依赖插件的,如果没有装置相干插件,则无奈应用对用办法;

App/Page/Component

当应用 MpKit.App/Page/Component 时,可传递多个对象,如:

import MpKit from '@mpkit/inject/dist/index';
// 如果在 config 中配置了 rewrite.App=true,则调用 App 等同于调用了[未重写的 App(MpKit.App)]
App(MpKit.App({
    globalData: {
        name: 'Tom',
        age: 10
    },
    onShow() {console.log('onShow1')
    },
    add(a, b) {return a + b;}
}, {
    globalData: {age: 20},
    onShow() {console.log(`onShow2, ${this.add(2, 4)}`);
        console.log(this.globalData);
    }
}));
// 输入:onShow1
// 输入:onShow2, 6
// 输入:{name: 'Tom', age: 20}


Component(MpKit.Component({
    data: {
        name: 'Alice',
        products: [
            {
                name: '苹果',
                price: 6
            },
            {
                name: '香蕉',
                price: 5
            }
        ]
    },
    created() {console.log('created1')
    },
    methods: {sayhi() {console.log(`hi ${this.data.name}`);
        }
    }
}, {
    data: {
        products: [
            {price: 8}
        ]
    },
    created() {console.log('created2');
        this.sayhi();
        console.log(this.data.list);
    },
    methods: {sayhi() {console.log(` 你好 ${this.data.name}`);
        }
    }
}));

// 输入:created1
// 输入:created2
// 输入:hi Alice
// 输入:你好 Alice
// 输入:[{ name: '苹果', price: 8}, {name: '香蕉', price: 5} ]

合并策略

从下面的例子能够看出合并策略是:

  • 属性进行深度合并
  • 办法会保留所有 mixin 中的办法体,依照程序全副执行

钩子函数

为能够进行全局拦挡 App/Page/Component/Api 上的办法,MpKit 做了钩子函数的机制,具体为:

  • 每个办法执行前会调用 before 钩子
  • 如果 before 钩子函数存在,且有返回值(如果有多个 before 钩子则取最初一个不为 undefined|true 的后果)时

    • 对于 App/Page/Component 如果返回值为false,则不会持续向下执行
    • 对于 Api 如果返回值为 false,则不会持续向下执行;同时如果返回值不为trueundefined时,会间接将后果返回进来,且不会持续向下执行
  • 而后执行 办法体,如果有多个,则顺次全副执行,并返回最初一个不为空的后果
  • 执行 after 钩子,并传入 办法体 的执行后果
  • 如果是 Api 上的异步办法,还会依据后果回调(success|fail)在执行 complete 钩子

MpKit 内置了很多钩子,用于全局事件触发、办法重写等,同时你能够增加本人的钩子函数,调用:

MpKit.MixinStore.addHook(type:MpViewType.App|MpViewType.Page|MpViewType.Component|'Api', hook:MpMethodHook)可为 App/Page/Component/Api 增加全局钩子函数;MpMethodHook的定义如下:

interface MpMethodHookLike {
    before?(
        methodName: string,
        methodArgs: any[],
        methodHandler: Function,
        funId?: string
    );
    after?(
        methodName: string,
        methodArgs: any[],
        methodResult: any,
        funId?: string
    );
    catch?(
        methodName: string,
        methodArgs: any[],
        error: Error,
        errType?: string,
        funId?: string
    );
    complete?(
        methodName: string,
        methodArgs: any[],
        res: any,
        success?: boolean,
        funId?: string
    );
}
interface MpMethodHook extends MpMethodHookLike {[prop: string]: Function | MpMethodHookLike;
}

示例 1:

import MpKit from "@mpkit/inject/dist/index";
import {MpViewType} from "@mpkit/inject/dist/types";
MpKit.MixinStore.addHook(MpViewType.App, {before(methodName, methodArgs) {console.log(`before methodName=${methodName}`);
    },
    after(methodName, methodArgs, methodResult) {console.log(`after methodName=${methodName}, ${methodResult}`);
    },
    catch(methodName, methodArgs, error) {console.log(`catch err=${error.message}`);
    },
});
App(
    MpKit.App({onLaunch() {this.add(1, 2);
        },
        onShow() {throw new Error("test");
        },
        add(a, b) {return a + b;},
    })
);
// 输入:before methodName=onLaunch
// 输入:before methodName=add
// 输入:after methodName=add, 2
// 输入:after methodName=onLaunch,
// 输入:before methodName=onShow,
// 输入:catch err=test

MpKit.MixinStore.addHook("Api", {before(methodName, methodArgs, methodHandler, funId) {console.log(`before api=${methodName}`);
    },
    after(methodName, methodArgs, methodResult, funId) {console.log(`after api=${methodName}, ${methodResult}`);
    },
    complete(methodName, methodArgs, res, isSuccess, funId) {console.log(`complete api=${methodName}, ${isSuccess}, ${res}`);
    },
});
MpKit.Api.request({url: "...",});
// 输入:before api=request
// 输入:after api=request, [RequestTask Object]
// 假如申请胜利且返回字符串“1”,则输入:complete api=request, true, 1
// 假如申请失败,则输入:complete api=request, false, {errMsg:'...'}

示例 2:当在 before 钩子中返回 false 会具体值时:

MpKit.MixinStore.addHook(MpViewType.App, {
    onShow: {before(methodName, methodArgs) {console.log("hook onShow");
            return false;
        },
    },
});
App(
    MpKit.App({onLaunch() {},
        onShow() {console.log("self onShow");
        },
    })
);
// 仅输入:hook onShow

const store = {};
MpKit.MixinStore.addHook("Api", {before(methodName, methodArgs, methodHandler, funId) {console.log(`before methodName=${methodName}`);
        if (methodName === "setStorageSync") {store[methodArgs[0]] = methodArgs[1];
        }
        if (methodName === "getStorageSync") {// 并不会真正执行(wx|my|tt|..).getStorageSync
            return store[methodArgs[0]];
        }
    },
    after(methodName) {console.log(`after methodName=${methodName}`);
    },
});
MpKit.Api.setStorageSync("name", "Tom");
const name = MpKit.Api.getStorageSync("name");
console.log(name === store.name);
// 输入:before methodName=setStorageSync
// 输入:after methodName=setStorageSync
// 输入:before methodName=getStorageSync
// 输入:true

结语

心愿 MpKit 能够为你带来便捷欢快的开发体验,祝大家工作顺心,家庭美满,加油!

可关注作者的其余开源我的项目:

  • 小松鼠:实用于 JavaScript 平台的数据上报工具
  • 小程序调试辅助工具:小程序控制台调试辅助工具
退出移动版