README.md

    wxMiniStore

    NPM version License

    一个基于原生小程序的 mini 全局状态管理库,跨页面/组件数据共享渲染。

    • 全局状态 state 支持所有 Page 和 Component,更新时使用独有 diff 能力,性能更强。
    • 周期监听 pageListener 能监听所有页面的 onLoad、onShow 等周期事件,方便埋点、统计等行为。
    • 全局事件 methods,一处声明,所有 wxml 直接可用的函数。
    • 适合原生小程序,即使后期引入,也只需增加几行代码。

    更新日志

    1.3.1

    [2021.1.13]
    U:优化pageListener中的onShareAppMessage能力,使其支持自定义全局分享

    1.3.0

    [2020.7.28]
    A:新增 store.prototype.clearState 清除状态,by @zkl2333
    F:新增polyfill,修复 #25
    F:单词错误 pageLisener 改为 pageListener(已做向下兼容可放心升级)。

    1.2.9

    [2020.3.31] A: 新增debug 字段,用于开启/关闭 setState 时的 console。

    导航

    开始

    在开始前,你可以 clone 或下载本项目,用微信开发工具打开 demo 目录来查看效果。

    1.安装及引入

    目前有两种引入方式:

    npm

    首先你需要 npm init 在项目目录下生成 package.json 后,再进行安装。

    npm init
    npm install wxministore -S

    然后在微信小程序右上角详情中勾选 使用npm模块
    接着选择左上角 工具-构建 npm。 这样你就可以在项目中导入了。

    //app.js中
    import Store from "wxministore";
    //或者 const Store = require('wxministore');
    App({});

    clone

    如果不太熟悉 npm 没关系,你可以将本项目中 lib/store.js 复制到你的项目中,并在app.js第一行引入:

    //app.js中
    import Store from "./util/store.js";
    //或者 const Store = require('./util/store.js');
    App({});

    2. 实例化一个全局状态 state

    Store 为构造函数,所以需要通过 new 关键字实例化,参数为 object 类型,下面我们初始化一个 state。

    let store = new Store({
      state: {
        msg: "这是一个全局状态",
        user: {
          name: "李四",
        },
      },
    });
    console.log(store.getState().msg); //这是一个全局状态 1.2.6+
    console.log(store.$state.msg); //这是一个全局状态 (不推荐)
    App({});

    初始化完成,我们如需在 js 中获取状态,可使用 store.getState() 获取全局状态,1.2.6+版本强烈推荐此方式。
    store.$state 也可获取,但不建议使用。

    3.在 App 中注入 store

    这么做是为了在其他页面中使用 store。

    App({
      onLaunch: function () {},
      store: store,
    });

    4.页面上使用

    在所有 wxml 中,可使用$state.x。 其中$state 为全局状态的容器,里面包含了所有的全局状态。

    <view>{{$state.user.name}}:{{$state.msg}}</view>

    显示为 李四:这是一个全局状态。

    如果在 template 文件中使用,需在属性 data 中引用$state

    <!-- 这是一个template -->
    <template name="t1">
      <view>{{$state.msg}}</view>
    </template>
    
    <!-- 这是引用位置 -->
    <template is="t1" data="{{$state,arg1,arg2}}" />
    <!--   相当于<template is="t1" data="{{$state:$state,arg1:arg1,arg2:arg2}}" /> -->

    在版本 1.2.1+建议使用 App.Page 和 App.Component 创建页面和组件,当然也不是必须。详情查看nonWritable

    // 没问题
    Page({
      //...
    });
    
    // 更好
    App.Page({
      //...
    });

    如果使用时,页面空白,说明你没有在 App 创建前 new Store。

    5.如何修改状态

    使用 app.store.setState 进行更新状态。如:

    const app = getApp();
    App.Page({
      data: {},
      onLoad: function () {
        //所有wxml中的$state.msg会同步更新
        app.store.setState({
          msg: "我被修改了,呜呜...",
        });
      },
    });

    修改状态注意事项

    // 错误的示范 视图不会更新
    let { user } = app.store.$state;
    user.name = "张三";
    app.store.setState({
      user,
    });
    
    //正确的示范
    let { user } = app.store.getState();
    user.name = "张三";
    app.store.setState({
      user,
    });

    获取全局状态需使用 app.store.getState()。

    周期监听 pageListener

    在有的场景,我希望每个页面在 onLoad 时执行一个方法(如统计页面,监听等)。原本做法是一个一个的复制粘贴,很麻烦。
    现在我们可以把某个周期,写入 pageListener 中,Store 会自动在相应周期优先执行pageListener然后再执行原页面周期内事件

    1.加入监听

    现在以监听 onLoad 为例, 在 Store 中新增一个 pageListener 对象,将需要监听的周期写入:

    // store中
    let store = new Store({
      //状态
      state: {
        //...
      },
      //方法
      methods: {
        //...
      },
      //页面监听
      pageListener: {
        onLoad(options) {
          console.log("我在" + this.route, "参数为", options);
        },
      },
    });

    就这样所有页面的 onLoad,将会优先执行此监听。接下来看页面内代码:

    // index/index.js 页面
    App.Page({
      onLoad() {
        console.log(2);
      },
    });

    执行结果为:

    // 我在index/index 参数为 {...}
    // 2

    2.全局分享 1.3.1+

    现支持全局分享功能,以方便开发者能一次性定义所有页面的分享功能。

    // store中
    let store = new Store({
      //页面监听
      pageListener: {
        onShareAppMessage(res){
          return {
            title: '全局分享',
            path: '/index/index'
          }
        }
      },
    });

    store中onShareAppMessage返回值的优先级是次于页面级的,所以当Page中有onShareAppMessage且有返回值,则会优先使用Page中的分享。

    3.没有第二步...

    总结:

    • 先执行 pageListener 监听,后执行原本页面中周期。
    • 还支持其他周期事件 ['onLoad', 'onShow', 'onReady', 'onHide', 'onUnload', 'onPullDownRefresh', 'onReachBottom', 'onShareAppMessage', 'onPageScroll', 'onTabItemTap']

    全局方法 methods

    新增 methods,全局可使用。 适用于各个 wxml 中的交互事件(bindtap 等), 你可以封装一些常用的交互事件,如 行为埋点,类型跳转等。

    1.创建一个全局方法

    在原有状态基础上,新增一个 methods 对象,写入你的全局方法:

    let store = new Store({
      //状态
      state: {
        msg: "这是一个全局状态",
      },
      //方法
      methods: {
        goAnyWhere(e) {
          wx.navigateTo({
            url: e.currentTarget.dataset.url,
          });
        },
        sayHello() {
          console.log("hello");
        },
      },
    });

    这里创建了一个全局封装的跳转 goAnyWhere。

    2.使用全局方法

    在 wxml 中,直接使用方法名调用:

    <view bindtap="goAnyWhere" data-url="/index/index">
      首页
    </view>

    在 js 中,直接使用 this.方法名 来调用:

    App.Page({
      onLoad() {
        this.sayHello();
      },
    });

    在非页面的 js 中,我们不建议使用 Store 中的全局方法。但你可使用 getCurrentPage().pop().sayHello() 来调用。

    3.说明

    • 尽量封装复用率高的全局方法
    • 非交互型事件(即非 bindxx)的公用方法,建议不写入 Store 中。写入 App 中更好。

    局部状态模式

    在项目的组件和页面越来越多且复用率越来越高时,全局$state的利用率就很低,这时候就出现了一种情况,页面中的组件和页面达到百千量级,每个内部都有一个$state,而用到它的可能就只有 1 个或几个。就会引起各种性能问题。比如更新$state十分缓慢,且低效。
    这时候你需要将$state 调整为部分组件和页面可用,而不是所有。

    1.开启局部模式

    let store = new Store({
      //。
      state: {
        msg: "这是一个全局状态",
      },
      openPart: true,
    });

    openPart 字段表示是否开启局部模式,默认值为 false。当我们想规定只有某些页面和组件使用$state 时,就需开启此模式,设置为 true。

    2.设置范围

    在需要使用$state的组件中,加入useStore: true,表示当前页面或组件可用$state。

    // a.js
    App.Page({
      useStore: true,
      onLoad() {
        console.log(this.data.$state); // { msg: '这是一个全局状态' }
        console.log(getApp().store.getState()); // { msg: '这是一个全局状态' }
      },
    });
    
    // b.js
    App.Page({
      onLoad() {
        console.log(this.data.$state); // undefined
        console.log(getApp().store.getState()); // { msg: '这是一个全局状态' }
      },
    });

    a 页面设置了 Store 可用,所以可以通过 this.data.$state 获取。 b 页面没有设置,所以为 undefined,但两个页面均可通过 store.getState()读取全局状态。

    <--! a页面有效 -->
    <view>{{$state.msg}}</view>
    
    <--! b页面无效 -->
    <view>{{$state.msg}}</view>

    3.注意事项

    • openPart 一旦开启,所有没有设置 useStore 的页面和组件将不能在 wxml 中使用$state。
    • 组件或页面.js 中,我们建议使用 getApp().store.getState()去获取全局状态,因为他没有限制。
    • 仅在 wxml 中需要用到$state 的页面和组件中开启 useStore。

    你可以 clone 或下载本项目,用微信开发工具打开 demo 目录来查看具体用法。

    页面中 useProp 属性 1.2.3+

    useProp 用于控制当前页面/组件,使用哪些状态,不传则所有状态均可在当前页面中使用。

    观察以下代码及注释:

    // App.js
    let store = new Store({
      state: {
        s1: "s1状态",
        s2: "s2状态",
      },
    });
    
    // A页面中
    App.Page({
      useProp: ["s1"], //指定使用s1
      onLoad() {
        console.log(this.data.$state); // { s1: 's1状态' }
        console.log(getApp().store.getState()); // { s1: 's1状态', s2: 's2状态' }
      },
    });
    
    // B页面中
    App.Page({
      useProp: ["s2"], //指定使用s2
      onLoad() {
        console.log(this.data.$state); // { s2: 's2状态' }
        console.log(getApp().store.getState()); // { s1: 's1状态', s2: 's2状态' }
      },
    });
    
    // C页面中
    App.Page({
      onLoad() {
        console.log(this.data.$state); // { s1: 's1状态', s2: 's2状态' }
        console.log(getApp().store.getState()); // { s1: 's1状态', s2: 's2状态' }
      },
    });

    useProp 是控制当前组件/页面使用哪些状态,而 useStore 是控制哪些组件/页面可使用 state 这个功能,两者可以同时作用。如:

    // App.js中
    let store = new Store({
      state: {
        s1: "s1状态",
        s2: "s2状态",
      },
      openPart: true,
    });
    
    // A页面中
    App.Page({
      useStore: true,
      useProp: ["s1"], //指定使用s1
      onLoad() {
        console.log(this.data.$state); // { s1: 's1状态' }
        console.log(getApp().store.getState()); // { s1: 's1状态', s2: 's2状态' }
      },
    });
    
    // B页面中
    App.Page({
      useProp: ["s1"], //指定使用s1 但没设置useStore,所以无效
      onLoad() {
        console.log(this.data.$state); // undefined
        console.log(getApp().store.getState()); // { s1: 's1状态', s2: 's2状态' }
      },
    });

    其他 1.2.9+

    实例化 Store 时,提供 debug 字段,用于开启/关闭框架内部 console 日志。 默认值为 true,即开启状态。如不需要,则设置 false 即可。

    new Store({
      debug: false, // 关闭内部日志的输出。
    });

    non-writable 解决方案 1.2.1+

    收到开发者的反馈,在小程序中使用插件时,会报错提示:

    // [non-writable] modification of global variable "Page" is not allowed when using plugins at app.json.
    // 在app.json中使用插件时,不允许修改全局变量 Page

    原因是 store 源码重写了 Page、Component 方法。

    1、开启防改写

    在你的 store 配置中,加入 nonWritable: true

    let store = new Store({
      nonWritable: true,
    });

    2、创建页面与组件调整

    将你所有页面与组件创建方法改为App.Page(...) 和 App.Component(...)

    //页面.js
    App.Page({
      data: {},
      onLoad: function () {},
    });
    
    //组件.js
    App.Component({
      data: {},
    });

    以上就解决了此问题。

    api

    这里列举了所有涉及到 Store 的属性与方法。

    new Store(options: Object) *已更新

    该函数使用 new 关键字返回一个 Store 类型的实例。 参数 options,为配置参数,
    options.state 为初始全局状态。
    options.methods 为全局方法。
    options.openPart 状态局部模式。
    options.pageListener 周期监听。
    options.nonWritable 是否重写 Page,Componenet。

    store.setState(data: Object, callback: Function)

    用于修改全局状态,用法与微信小程序的 Page.prototype.setData 完全一致。 提示:页面中应避免使用 this.setData({$state: ...})去操作当前页面下的$state。如有相关需求,请使用页面其他状态存储。

    store.$state: Object

    该对象为实例.$state, 返回的是全局状态,应避免直接操作修改它。

    store.$r: Array

    该对象为所有页面或组件的实例。

    store.getState: () => Object 1.2.6+

    该 api 返回的是全局状态的拷贝。

    store.clearState(callback: Function) 1.3.0+

    用于清空全局状态,使所有$state下任意的状态为undefined。

    总结及建议

    考虑到后期的 app.js 内 store 不直观,可以把整套 store 单独写入一个 js 中,通过 require 引入。如:

    // mystore.js中
    const Store = require("../util/store.js");
    module.exports = new Store({
      state: {},
      methods: {},
    });
    //---------------------------
    // app.js中
    let store = require("store/mystore.js");
    App({
      store,
    });

    MiniStore 非常适合原生小程序。可以随时引入,不影响原有的业务,拓展性强。 欢迎 star、欢迎提 issue 甚至 pr...

    License

    MIT © Leisure

    项目简介

    🚀 Github 镜像仓库 🚀

    源项目地址

    https://github.com/xiaoyao96/wxMiniStore

    发行版本 2

    v1.3.1

    全部发行版

    贡献者 5

    开发语言

    • JavaScript 100.0 %