# wxMiniStore
[![NPM version](https://img.shields.io/npm/v/wxministore.svg)](https://www.npmjs.com/package/wxministore)
[![License](https://img.shields.io/npm/l/wxministore.svg)](https://www.npmjs.com/package/wxministore)
一个基于原生小程序的 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能力,使其支持自定义[全局分享](#share)。
### 1.3.0
\[2020.7.28\]
`A`:新增 [store.prototype.clearState](#clearState) 清除状态,by [@zkl2333](https://github.com/zkl2333)
`F`:新增polyfill,修复 [#25](https://github.com/xiaoyao96/wxMiniStore/issues/25)。
`F`:单词错误 pageLisener 改为 pageListener(已做向下兼容可放心升级)。
### 1.2.9
\[2020.3.31\] `A`: 新增[debug 字段](#other),用于开启/关闭 setState 时的 console。
### 导航
- [全局状态开始](#start)
- [安装及引入](#start-1)
- [实例化](#state)
- [App 中注入](#start-3)
- [页面上使用](#start-4)
- [修改状态](#start-5)
- [修改状态注意事项](#start-6)
- [页面周期监听](#lisener)
- [全局分享](#share)
- [全局方法](#f)
- 性能优化
- [局部状态模式](#part)
- [useProp](#useProp)
- [其他](#other)
- [non-writable 解决方案](#nonWritable)
- [Api 说明](#api)
- [总结及建议](#end)
## <div id="start">开始</div>
在开始前,你可以 clone 或下载本项目,用微信开发工具打开 demo 目录来查看效果。
### <div id="start-1">1.安装及引入</div>
目前有两种引入方式:
#### npm
首先你需要 npm init 在项目目录下生成 package.json 后,再进行安装。
```cmd
npm init
npm install wxministore -S
```
然后在微信小程序右上角详情中勾选 `使用npm模块`。
接着选择左上角 工具-构建 npm。
这样你就可以在项目中导入了。
```js
//app.js中
import Store from "wxministore";
//或者 const Store = require('wxministore');
App({});
```
#### clone
如果不太熟悉 npm 没关系,你可以将本项目中 lib/store.js 复制到你的项目中,并在`app.js第一行`引入:
```js
//app.js中
import Store from "./util/store.js";
//或者 const Store = require('./util/store.js');
App({});
```
### <div id="state">2. 实例化一个全局状态 state</div>
Store 为构造函数,所以需要通过 new 关键字实例化,参数为 object 类型,下面我们初始化一个 state。
```js
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 也可获取,但不建议使用。
### <div id="start-3">3.在 App 中注入 store</div>
这么做是为了在其他页面中使用 store。
```js
App({
onLaunch: function () {},
store: store,
});
```
### <div id="start-4">4.页面上使用</div>
在所有 wxml 中,可使用$state.x。
其中$state 为全局状态的容器,里面包含了所有的全局状态。
```html
<view>{{$state.user.name}}:{{$state.msg}}</view>
```
显示为 李四:这是一个全局状态。
如果在 template 文件中使用,需在属性 data 中引用\$state
```html
<!-- 这是一个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](#nonWritable)
```js
// 没问题
Page({
//...
});
// 更好
App.Page({
//...
});
```
如果使用时,页面空白,说明你没有在 App 创建前 new Store。
### <div id="start-5">5.如何修改状态</div>
使用 app.store.setState 进行更新状态。如:
```js
const app = getApp();
App.Page({
data: {},
onLoad: function () {
//所有wxml中的$state.msg会同步更新
app.store.setState({
msg: "我被修改了,呜呜...",
});
},
});
```
### <div id="start-6">修改状态注意事项</div>
```js
// 错误的示范 视图不会更新
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()。
## <div id="lisener">周期监听 pageListener</div>
在有的场景,我希望每个页面在 onLoad 时执行一个方法(如统计页面,监听等)。原本做法是一个一个的复制粘贴,很麻烦。
现在我们可以把某个周期,写入 pageListener 中,Store 会自动在`相应周期优先执行pageListener然后再执行原页面周期内事件`。
### 1.加入监听
现在以监听 onLoad 为例, 在 Store 中新增一个 pageListener 对象,将需要监听的周期写入:
```js
// store中
let store = new Store({
//状态
state: {
//...
},
//方法
methods: {
//...
},
//页面监听
pageListener: {
onLoad(options) {
console.log("我在" + this.route, "参数为", options);
},
},
});
```
就这样所有页面的 onLoad,将会优先执行此监听。接下来看页面内代码:
```js
// index/index.js 页面
App.Page({
onLoad() {
console.log(2);
},
});
```
执行结果为:
```js
// 我在index/index 参数为 {...}
// 2
```
### <div id="share">2.全局分享 `1.3.1+`</div>
现支持全局分享功能,以方便开发者能一次性定义所有页面的分享功能。
```js
// 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']
## <div id="f">全局方法 methods</div>
新增 methods,全局可使用。
适用于各个 wxml 中的交互事件(bindtap 等), 你可以封装一些常用的交互事件,如 行为埋点,类型跳转等。
### 1.创建一个全局方法
在原有状态基础上,新增一个 methods 对象,写入你的全局方法:
```js
let store = new Store({
//状态
state: {
msg: "这是一个全局状态",
},
//方法
methods: {
goAnyWhere(e) {
wx.navigateTo({
url: e.currentTarget.dataset.url,
});
},
sayHello() {
console.log("hello");
},
},
});
```
这里创建了一个全局封装的跳转 goAnyWhere。
### 2.使用全局方法
在 wxml 中,