Coder Social home page Coder Social logo

wxministore's Introduction

wxMiniStore

NPM version License

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

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

更新日志

自 2022.8.31 起本人将不再更新维护此库。 推荐大家使用腾讯开源的 westore 来管理微信原生小程序状态,或直接使用 uniapp、taro 等跨平台开发框架来开发小程序更佳。 感谢之前使用过此库的开发者,以及提过pr和issue的贡献者。

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

wxministore's People

Contributors

dependabot[bot] avatar qq675258207 avatar xiaoyao96 avatar zkl2333 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

wxministore's Issues

建议页面可以自定义只用到的store属性

像useStore属性,传一个数组为store.$state里的属性
例如 const store = new Store({a:1, b:2, c:3})
在Compoent({useProps: ['a']}), 这样这个页面的this.data.$state里就只挂载a属性

在new Store({state:{ },methods:{ })中,methods中的方法无法改变state 中的属性值,这个问题如何解决

业务场景:

登录的状态,token,与登录刷新token的方法,需要在页面里面调用登录,刷新token等逻辑的方法,目前关于token,登录态的处理是在页面方法的回调中改变的state中的相关token与登录态,当;逻辑比较复杂时,维护比较困难。代码如下:

// store.js
let store = new Store({
  state:{
     token:"",
     hasLogin:false,
   },
   methods:{
       // 刷新token
     refreshToken(token){
       //  ......
       // 无法直接设置state中的token
     },
   }
})

请问可以支持以上的设置吗?

Object.entries

请问怎样在mystore.js,method方法中修改state中的值,其他页面调用这个方法

淘宝小程序

我将这个库 迁移到淘宝小程序了,目前测试 page之间用store是没问题 可以获得的,
page和组件之间通讯 目前有问题 ,希望可以 探讨下怎么适配到淘宝小程序 ,我已经加你微信,麻烦通过 下
我的wx:110837675

setState后无法回调

在手机端的时候,经常出现,app.store.setState后无法执行回调的情况。

小程序的异步机制导致store获取数据为空,如何处理

在App.js的onLaunch中请求后台api获取用户信息,然后保存到store中
然后在首页onLoad中从store中获取用户信息,结果是空
我知道这是由于异步导致的,如何在页面中监听到store中有值了,然后获取值,并且渲染到页面中

完整更新历史

更新日志

1.2.8

[2019.11.27] F: 优化diff能力。

1.2.7

[2019.9.6] F: 修复删除state中的数组,删除的项会为null的问题。

1.2.6

[2019.6.26] A: 新增store.prototype.getState,用于读取store.$state的拷贝,防止对原状态进行误操作。

1.2.5

[2019.6.25] F: 修复setState为引用类型数据时视图可能不会更新。

1.2.3

[2019.4.15] A: 组件/页面内 新增useProp属性,指定当前组件/页面使用指定状态,提高性能。
[2019.4.15] U: store.setState加入diff算法,更加高效。

1.2.2

[2019.4.2] F: 修复pageLisener内部分周期执行时报错。

1.2.1

[2019.3.24] F: 修复使用plugins插件时,提示non-writable错误。(解决方案)
[2019.3.24] F: 对Component中lifetimes字段的兼容。

1.2

[2019.3.21] F:测试发现小程序原生的setData会默认深拷贝目标对象。所以导致了#3,目前已修复,也说明了setState后,各个页面的$state不再是完全引用的关系。
[2019.3.17] U:不再支持es5,并重写源码(es6),代码更少,做的更多,又恢复了$state完全引用(仅首次加载),修复了一些极端情况的bug。

1.1

[2018.11.29] A:新增局部状态模式, 可设置$state部分页面或组件可用,大幅提升性能。
[2018.11.16] A:支持es5。
[2018.10.31] A:拓展新增周期监听 pageLisener字段,可监听所有页面的所有生命周期事件。
[2018.10.30] A:拓展新增功能全局方法 methods字段,大幅优化setState性能。更新前需调整Store结构,请阅读Store对象参数详情

关于 onShareAppMessage 是采用 pageListener 会失去图片

由于 onShareAppMessage 、 onReachBottom 、onPullDownRefresh等是触发性事件函数,所以不能归类为生命周期,也就是说,不能直接 pageListener[key].apply(this, arguments);对于触发性事件函数,我建议单独归类,同时采用赋值 o[key] = pageListener[key],但是有个前提就是 o[key]无效,才采用公用的。建议下个版本修改一下!

生命周期是直接执行,所以能够 pageListener[key].apply(this, arguments),触发事件不会直接执行,所以必须采用赋值,等到触发时执行!

官网解释:
https://developers.weixin.qq.com/miniprogram/dev/reference/api/Page.html#onshareappmessageobject-object

期待数据监听更新啊

在App.js的onLaunch()中请求后端获取userInfo。然后要在首页index.js中监听到userInfo后,才能进行其它的业务,可是目前没这个功能啊,总不能用定时器去循环等待吧,太美中不足了,期待能早点更新数据监听

pageLisener 中如果使用onShareAppMessage会使得return值无效

` /* store.js */
pageLisener: {
onShareAppMessage(ops) {
if (ops.from === "menu") {
console.log("ShareAppMessage menu")
return {
title: "自定义标题1",
path: "/pages/loading/loading",
imageUrl: "xxxx"
}
}
}
}

这样设置,真机和开发者工具上 title ,imageUrl 无效; 另外如果我在pageA页面扩展了onShareAppMessage
/* pageA.js */
onShareAppMessage(ops) {
if (ops.from === "button") {
console.log("ShareAppMessage button")
return {
title: "自定义标题2",
imageUrl: 'xxxxxx',
path: "/pages/pDetail/pDetail?xxxx"
}
}
console.log('share end')
}

`
当点击右上角的分享 (ops.from === "menu"),控制台依然会输出‘share end’,
当点击自定义的分享 (ops.from === "button"),控制台不会输出‘share end’,
且2种情况title,imageUrl 都无效

首次下载小程序包,在小程序直播插件分包内调用单独的request.js getCurrentPages拿不到store内methods的方法

复现场景
用户为第一次下载小程序,进入小程序直播分包内,app.js调用request.js请求
返回结果后使用getCurrentPages().pop()为空 拿不到store methods内的方法
app.js内onLaunch和onShow第一时间拿不到getCurrentPages().pop()内方法需要加延时
其他情况是可以拿到的 在非page和components内还有其他方法可以拿到methods方法么
有什么方法可以兼容一下么 还是我的打开方式不对😂

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.