当前小程序项目全局状态及逻辑判断太复杂,尝试引入状态管理优化逻辑,同时将重复的逻辑判断放到getters中。这里我们使用mobx4,因为mobx使用了小程序不兼容的proxy。
mobx4介绍
主要是这几个api:
- observable
- get
- autorun
- action
我们引入observable创建一个store,创建一些观察对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import {observable, autorun} from 'mobx'
const store = observable({ value: 1, list: [], get count () { return this.list.count } setValue (val) { this.value = val } addItem (item) { this.list.push(item) } })
autorun(() => { console.log(store) })
|
其中get可以创建计算属性,其他函数为action,修改状态值。当值发生修改时,可触发autorun函数。
observable状态对象需要通过toJS转换为js对象。
引入mobx状态管理
小程序引入mobx,可以用observable创建一个store对象,然后类似react中使用,需要创建一个connect函数,来把需要的状态值转换为js对象,再注入页面或组件对象。
这里我找了一个第三方库mobx-wxapp,下面是它的connect方法和使用。
connect方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
|
function connect (context, mapDataToStore, options = {}) { if (!isTypeFunction(mapDataToStore)) { throw new TypeError('mapDataToStore 必须是一个function') }
const callback = options.setDataCallback || (() => {})
let tempdata = {} const update = nextdata => { Object.assign(tempdata, nextdata) const newValue = diff(context.data, tempdata) console.log('new data:', newValue) context.setData(newValue, () => { callback(newValue) }) tempdata = {} } const func = mapDataToStore mapDataToStore = function () { const data = func() for (let k in data) { const item = data[k] if ( isObservableObject(item) || isObservableArray(item) || isObservableObject(item) || isBoxedObservable(item) || isObservableMap(item) ) { data[k] = toJS(item) } } update(data) } const disposer = autorun(mapDataToStore) const onUnload = context.onUnload if (onUnload) { context.onUnload = function () { disposer() onUnload.apply(context, arguments) } } return disposer }
|
其中的防抖函数被我注释了,虽然对性能有优化,但会造成我之前代码的回调触发顺序错误。
可以看到核心代码就是在connect函数执行时,将原先的mapDataToStore转换为js对象去合并旧的data对象,然后执行authrun函数去监听变化,当发生变化时,执行重写的mapDataToStore方法,将每个状态值转换为js对象,再setData到当前context。
在页面使用:
1 2 3 4 5 6 7 8 9
| Page({ onLoad() { connect(this, () => ({ value: store.value, list: store.list, }) ) }, })
|
在组件使用:
1 2 3 4 5 6 7 8 9 10 11 12
| Component({ ready(){ this.disposer = connect(this,mapDataToStore,options) },
detached(){ this.disposer(); } })
|
存在的分包问题及解决方式
在实际使用中,发现跨分包时,引入的store对象的值都是初始值,而不是主包store的当前状态。其实,我们去看一下文档就能发现,es模块的import多次引入同一个文件,只会执行一次,后面再次引入会直接拿到这个对象,所以我们可以在各个js文件import同一个模块,而不用担心两个引入的对象不一样。

但是小程序如果开启了分包,情况就变了,每个分包的js环境都是相对独立的,所有不同分包的store对象并不相同。

因此,我们不能直接在各个分包import这个store模块,而是应该在app.js引入一次,然后把它挂到公用的app对象上。之前还遇到了消息队列在分包不等待主包阻塞的问题,应该也是同一个原因,这次一起把api挂到app上。
1 2 3 4 5 6 7 8 9 10
| import * as apis from '/apis/api' import globalStore from '/stores/index'
App({ stores: { globalStore, }, apis, ... })
|
拆分store及依赖关系
随着业务的复杂,我们需要将store按照不同业务进行拆分,以便更清晰的管理状态和业务。这多个store是怎样的结构,相互间的依赖关系呢?我们可能存在一些公用状态,然后其他store的计算属性依赖了这个状态。
按照官网的例子,如果store是用class创建的,可以在new其他store时,在构建函数传入rootStore,再把rootStore挂在子store上以便访问,也可以把子store挂载rootStore上。
这里我们之前是用observable直接创建的一个对象,就不改成class了。在store目录新建多个store文件,分别导出各个store的对象。在index.js中引入所有store,一起export。
1 2 3 4 5 6 7
| import globalStore from './globalStore.js' import subscribeStore from './subscribeStore.js'
export default { globalStore, subscribeStore, }
|
在app.js也是引入整个stores,挂在app上。
1 2 3 4 5 6
| import stores from '/stores/index'
App({ stores, ... })
|
重点是,在subscribeStore中新建一个计算属性,依赖globalStore中的属性进行计算。当我们调用这个属性是,当然会触发,但是我们修改globalStore中的属性,这个计算属性是否会自动执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import { observable } from '/utils/mobx/mobx'
import globalStore from './globalStore'
const subscribeStore = observable({ selectMemberLevel: '2',
get testFlag () { consoel.log('globalStore.userInfo has changed') return globalStore.userInfo && globalStore.userInfo.mciGyUd != null && globalStore.userInfo.mciGyId !== '' },
setData (data) { for (const key in data) { this[key] = data[key] } }, })
export default subscribeStore
|
如上,我们在subscribeStore中创建了一个计算属性,依赖globalStore的userInfo。再在页面js中connect注入这个属性,在页面axml绑定渲染,然后我们在控制台中获取app.globalStore,手动修改userInfo,可以看到控制台触发了这个console,并且页面渲染改变了。说明在一个store中可以监测到另一个store的依赖变化。
注意:不要循环依赖。公用的状态都放到globalStore中。