科技微讯

微信小程序如何使用 MobX 进行状态管理?

微信小程序官方在 2019 年针对小程序发布了 MobX 辅助绑定库,可以让我们在微信小程序中使用 Mobx 进行状态管理:

这篇文章和大家分享为什么要使用 MobX,我对 Mobx 核心概念的理解,以及如何在微信小程序中使用 Mobx。

为什么要用 Mobx

关于为什么要在小程序使用 MobX,可以看小程序 MobX 绑定库官方开发者的一个回复

打包成 json 页面传值不是也很香吗?跨的页面比较多使用全局变量应该也可以吧?

那样的话每次数据变动都得传,是“过程式”的方法。状态管理框架在处理数据项需要在多个页面间同步的情况中会更加方便。

再看另一个问答

在小程序中,可使用 mobx-miniprogram 配合 mobx-miniprogram-bindings 实现全局数据共享,也可以用 globalData 是实现数据共享。请问登录之后的用户信息应该放在哪里?

globalData 一般适用于放置一些极少改变的全局状态。事实上,也可以通过 require 一个独立的 js 文件来代替 globalData 。MobX (和类似的状态管理库)适用于维护需要跟踪更新的界面状态数据。所以用户信息还是建议放在 globalData 里管理。

Mobx 的核心概念

要在微信小程序中使用 MobX,首先要了解 MobX,要了解 MobX,首先要知道 MobX 的三个核心概念:

const { observable } = require("mobx-miniprogram");
const myObservable = observable({ name: "kejiweixun" });
const { observable, action } = require("mobx-miniprogram");
const state = observable({ value: 0 });
const increment = (state) => {
  state.value++;
};
//即使没有使用 action 函数,increment 也是一个 action,
//因为它修改了 observable,但建议总是使用 action 函数进行显式声明
const incrementAction = action(increment);
increment(state);

以上三个核心概念的关系可以通过下面这张图片理解:

mobx-overview

在微信小程序中使用 Mobx

在微信小程序中使用 MobX 需要同时安装以下两个依赖,这在开头就说过了。第一个依赖其实是 MobX 官方项目的 fork,把它理解成 mobx 即可;第二个依赖是针对小程序的 binding,MobX 为多个 web 框架分别开发了对应的 binding,例如 React 的是 mobx-react,Vue 的是 mobx-vue

构造一个小程序页面有两种方法,一种是使用 Page 构造器,另一种是使用 Component 构造器,这里只分享使用 Component 构造器构造页面时 mobx-miniprogram-bindings 的使用。以下是一个例子:

// store.js,在这个文件中创建一个 observable 对象
import { observable, action } from "mobx-miniprogram";
export const store = observable({
  // 数据字段
  numA: 1,
  numB: 2,
  // 计算属性,前面说过了,getter 会被 observable() 处理成一个 computed value
  get sum() {
    return this.numA + this.numB;
  },
  // actions,前面说过了,修改了数据字段的代码就是 action,需要用 action() 显式声明
  update: action(function () {
    const sum = this.sum;
    this.numA = this.numB;
    this.numB = sum;
  }),
});
//使用 Component 构造小程序页面,引入 storeBindingsBehavior,并增加 storeBindings 属性,该属性规定有 store、fields、actions 三个字段
import { storeBindingsBehavior } from "mobx-miniprogram-bindings";
import { store as myStore } from "./store.js";
Component({
  behaviors: [storeBindingsBehavior],
  storeBindings: {
    store: myStore, //使用 observable 函数创建的 observable 对象,常被叫做 store
    fields: ["numA", "numB", "sum"], //observable 对象中的数据属性、计算属性(getter)
    actions: ["update"], //observable 对象中的 action
  },
});

mobx-miniprogram-bindings 的核心是它 export 的 storeBindingsBehavior,它的作用主要有三个:

总结一下,mobx-miniprogram-bindings 的核心是 storeBindingsBehavior,而 storeBindingsBehavior 的核心是 createDataFieldsReactions、createActions。顾名思义,createDataFieldsReactions 是为 storeBindings.fields 中的各字段创建自定义 reaction,createActions 是把 storeBindings.actions 中的各 action 函数挂载到页面下方便调用。

需要注意的是,storeBindings.actions 的各 action 会被挂载到页面的 this.methods 下,所以我们可以像 this.update() 这样调用它,而 storeBindings.fields 并没有被挂载到 this.data 下,但我们依然可以像 this.data.numA 这样访问它的值,为什么?这是因为 createDataFieldsReactions 函数所创建的 reaction 在调用 setData 时,会把值 set 到页面中同名的 data 字段中,比如 numA 这个 field 所对应的 reaction 会这样 setData:this.setData({numA: 1})。于是,页面本来是没有 this.data.numA 这个字段的,被这个 reaction 一番操作后,就多了这么一个字段。

createDataFieldsReactions 在创建一个 reaction 时,把 fireImmediately 设置为了 true,如果把它改为 false,在页面的 onload 函数的开头位置访问 this.data.numA 会得到 undefined,这充分证明了这个页面本来确实没有 this.data.numA,后来之所以有了,是被 reaction setData 上去的。

所以要注意了,我们不应该在页面的 js 文件中使用 setData 赋值给与 storeBindings.fields 同名的字段,正确的做法应该是使用 storeBindings.actions 修改 storeBindings.fields 字段的值,剩下的就交给 MobX,你可以相信 MobX 会把这个值渲染到界面中,如果这个值发生了变化的话。

MobX 的原则是:

Make sure that everything that can be derived from the application state, will be derived. Automatically.

这也应该是我们使用 MobX 的原则:定义最基本的 state,所有可以从这些 state 衍生出来的 computed value 或 reaction,都应该让 MobX 自动衍生,避免人为干预。所有修改 state 的函数或方法都应该显式声明为 action,我们只负责触发 action,剩下的都交给 MobX:action 触发会导致 state 发生改变,这进一步会导致 computed value 发生改变,以及 reaction 做出反应,这些都是由 MobX 自动进行的,交给 MobX 吧,我们只负责触发 action。

使用 MobX 容易让人产生困惑的一点是,如果一个 observable 对象的某个属性的值是一个对象(下方示例的 name),并且某个 action 被触发后只修改了该对象的某个属性(下方示例的 name.first),那 mobx-miniprogram-bindings 是不会把修改后的值渲染到界面中的。

示例代码:

<!-- index.html -->
<view>First Name: {{name.first}}</view>
<view>Last Name: {{name.last}}</view>
<button bindtap="handleTap">点击更换 First Name</button>
//index.js
import { storeBindingsBehavior } from "mobx-miniprogram-bindings";
import { store } from "./store";

Component({
  behaviors: [storeBindingsBehavior],
  storeBindings: {
    store,
    fields: ["name"],
    actions: ["changeFirstName"],
  },

  methods: {
    handleTap: function () {
      this.changeFirstName("Williams");
    },
  },
});
//store.js
import { observable, action } from "mobx-miniprogram";

export const store = observable({
  name: {
    first: "Elon",
    last: "Musk",
  },
  changeFirstName: action(function (firstName) {
    //1、需要替换整个对象:
    this.name = Object.assign({}, this.name, { first: firstName });
    //2、只是更新 name 对象的某个属性,是不会引起界面变化的,如:
    //this.name.first = firstName;
  }),
});

这个例子表明,更改某个值为 object 的 state 时,不能只更改这个 state 的某个属性,而应该替换整个 state,否则不会引起界面变化

总结

在微信小程序中使用 MobX,需要安装 mobx-miniprogrammobx-miniprogram-bindings,接着在一个单独的 js 文件(通常命名为 store.js)中使用 MobX 下的 observable 函数创建一个 observable 对象,以用作存放应用状态的 store。

我们通常会在 observable 对象中定义一些数据属性、computed value、action。在小程序页面中,建议使用 Component 构造器构造页面,在页面增加 storeBindings 字段,并从 mobx-miniprogram-bindings 导出 storeBindingsBehavior,该 behavior 会依据 storeBindings.fields 创建相应的 MobX reaction,同时把 storeBindings.actions 挂载到页面的 this.methods 对象下。

当一个页面需要更新 store 中的某个 state 时,我们只需要触发相应的 action,剩下的都交给 MobX:action 会修改 state,state 的改变会引起 computed value 的自动更新,state 和 computed value 的改变都会引发 reaction,在小程序中,就是把更新后的值渲染到页面中展示。

需要特别注意的是,如果更新的 state 是一个对象,需要替换整个对象,如果只更新该对象的某个属性,是不会引起界面变化的。


相关阅读:

thumbsup0
thumbsdown0
暂无评论