mobx-state-tree

MobX State Tree (MST)是 MobX 开发者基于 MobX 开发的一个状态管理工具。MST 在保留 MobX 的设计理念的同时结合了 Redux 中只有一个 Store 和不可变数据的特点。

如果要在应用项目中使用 MST,需要同时安装 MST 和 MobX。

npm install mobx mobx-react-lite mobx-state-tree
yarn add mobx mobx-react-lite mobx-state-tree

MST 的核心部分就是一个动态树,它由严格保护的可变对象和运行时信息组成。每一棵树都是有一个类型结构信息和一个状态数据组成。

类型结构信息通常使用 types 对象提供的方法来定义。类型结构的元信息的定义过程一般是使用 types.Model() 定义一个对象结构,然后在其中使用 types 中的类型标记声明其中的具体字段类型(节点类型),并以此来形成 MST 树结构。Model 中的节点不仅可以是基础类型,还可以是其他的 Model,这样 MST 就可以通过层层嵌套形成一棵 MST 状态管理树。

以下是一棵简单的 MST 示例。

import { types } from 'mobx-state-tree';

const Todo = types.model('Todo', {
  title: types.optional(types.string, ''),
  done: types.optional(types.boolean, false)
});

const User = types.model('user').props({
  name: types.optional(types.string, '')
});

const RootStore = types.Model({
  users: types.map(User),
  todos: types.optional(types.map(Todo), {})
});

MST 中常用的类型定义有以下这些:

  • types.model() ,定义一个 Model,其中包含属性和 Actions。
  • types.array(type) ,定义一个包含指定类型的数组。
  • types.map(type) ,定义一个包含指定类型的 Map。
  • types.string ,字符串。
  • types.number ,数字类型。
  • types.boolean ,布尔类型。
  • types.Date ,日期类型。
  • types.union() ,多类型的联合类型。
  • types.optional() ,可选值类型。
  • types.literal() ,字面量类型。
  • types.enumeration(name, options) ,枚举类型。
  • types.refinement() ,基于基础类型的增强类型。
  • types.maybe() ,可选或者可空类型。
  • types.null ,空值类型。
  • types.undefined ,未定义类型。
  • types.late() ,延迟定义的递归类型。
  • types.frozen ,可串行化类型。
  • types.compose() ,组合类型。
  • types.identifier() ,标识符类型。
  • types.reference(type) ,引用类型。

定义了 MST 的初始值和类型以后,可以通过定义 Action 来更改 MST 中的值。Action 的定义可以利用 model.action() 方法来定义。

const Todo = types.model({
  id: types.number,
  title: types.string
})
.action(self => ({
  setTitle(val: string) {
    self.title = val;
  }
  const fetchTitles = flow(async function() {
    // 使用 await 完成一系列异步操作
  })
}));

// 创建一个树并初始化数据
const todo = Todo.create({ title: "Something" });

// 验证一下数据的变化
console.log(todo.title);
Todo.setTitle("Another thing");
console.log(todo.title);

在使用 Action 的时候需要注意以下几点:

  • 节点内容的修改必须在 Action 中进行。
  • 每次实例化的时候,初始化函数都会执行,所以 self 始终指向当前实例。
  • Action 内部不要使用 this ,因为 this 的指向性不够明确。
  • 在 MST 中使用异步 Action 的时候,可以通过引入 flow 来包装。

相对应于 MobX 中数据的派生,在 MST 中是通过 View 来定义的。MST 中的 View 有两种形式,有参数的 View 和无参数的 View。其中没有参数的 View 实际上就是 Computed。这两种 View 的区别就是 Computed 有明确的缓存点,其派生的值会被缓存到它依赖的数据发生变化。而有参数的 View 则不会对派生的值进行缓存。

View 的定义与 Action 类似,是使用 model.view() 完成的。

const Todos = types
  .model({
    children: types.array(Todo)
  })
  .view(self => ({
    get amount() {
      return self.children.length;
    }
  }));

MobX 中的数据都是可变数据,MST 中的数据也都是会经常变化的。但是在 UI 渲染的时候,不可变的数据的可预测可回溯特性不会出现数据无法渲染的问题。从 MST 中获取不可变数据,就需要用到快照功能。

MST 中快照相关的方法主要有三个:

  • getSnapshot(model) ,返回一个 MST 的当前状态。
  • onSnapshot(model, callback) ,创建一个当新快照可用的时调用回调函数的监听器。
  • applySnapshot(model, snapshot) ,使用快照更新 MST 及其所有后代的状态。

当 MST 与 React 结合的时候,可以通过类似与前面示例中的 RootStore.create() 来获取 Store 实例。

Warning

虽然大部分情况下 MST 均提供了针对性能的优化,但是如果应用中存在大量会频繁发生变化的数据时,原生 MobX 的性能会更好。