Mobx-state-tree介绍

更新于 阅读 2
Mobx-state-tree介绍

介绍

Mobx-state-tree(简称MST)是基于mobx的响应式状态管理工具,核心思想就是一个动态树,每个树由一个结构和一个状态组成,支持状态的序列化、反序列化、时间旅行等功能。

使用

npm install mobx mobx-state-tree --save

yarn add mobx mobx-state-tree

types

如下是一个后端对象结构描述,前端怎么保证数据类型的一致性。或者是修改时过程中数据类型不会发生变化

type Article struct { ID string `json:"id"` // 文章ID Name string `json:"name"` // 文章名称 CreateTime int64 `json:"createTime` // 创建时间 IsDeleted bool `json:"isDeleted"` // 是否删除 }

MST提供了一个类型校验机制,通过设置类型就可以定义整个状态的树形结构。在开发的时候就可以判断类型是否匹配并提前给出错误提示。

MST提供了一个types 的对象,其中包含基础类型:

  • string
  • number
  • boolean
  • integer
  • Date

工厂方法:

  • model
  • array
  • map
  • optional

其中model用来创建Model,在model中可以包含多个type,通过type的组合就可以定义出整颗状态树。

Model定义好后,使用Model.create创建实例,传入的第一个参数作为默认值,如果没传将使用optional中设定的值作为默认值;第二个个参数为可选参数(环境配置对象env),通过env实现依赖注入。demo

// 文章model const Article = types .model("Article", { id: types.string, name: types.string, createTime: types.integer, isDeleted: types.optional(types.boolean, false) }) .actions((self) => ({ setName: (value) => { self.name = value; } })) // 文章实例 const article = Article.create({ id: "1", name: "测试", createTime: new Date().getTime() }); // 文章列表model const ArticleList = types.model("ArticleList", { list: types.array(Article), current: types.reference(Article) });

上面创建了两个model ArticleAritcleList,虽然代码看起来有点繁琐,但是比起将状态放在store中,粒度其实更小了,可以更好的复用。

当通过setName方法设置name的值时,不是string类型就回报错

article.setName(123)
Error
[mobx-state-tree] Error while converting `123` to `string`:

    value `123` is not assignable to type: `string` (Value is not a string).

设置默认值,使用optional可以给字段设置默认值

const Article = types .model({ id: types.string, name: types.string, createTime: types.integer, isDeleted: types.optional(types.boolean, false) })

props

props指的是Model中定义的属性,定义了model包含哪些字段、以及对应的类型。

const Article = types .model("Article", { name: types.string, isDeleted: types.optional(types.boolean, true) })

第一个参数为Model的名称,第二个参数就是props。model的第二个参数可以省略,如下:

const Article = types .model("Article") .props({ name: types.string, isDeleted: types.optional(types.boolean, true) })

定义好props之后,可以直接访问到对应的字段

const article = Article.create({name: "测试",}); console.log(article.name);

当数据需要通过计算得到时可以使用Views

Views

views是Model中定义或获取衍生数据的方法的集合,只能获取值不能设置值

const Article = types .model("Article", { id: types.string, name: types.string, createTime: types.integer, count: types.optional(types.integer, 1), isDeleted: types.optional(types.boolean, true) }) .views((self) => ({ get formatedName() { return self.name + "_" + self.id; }, getFormatedName() { return self.name + "_" + self.id; } }))

上面使用的是getter的形式获取数据,数据将会被缓存直到依赖的数据发生变化。否则每次都需要通过函数调用的方式来获取数据,无法对计算结果进行缓存,推荐使用getter的方式,提升性能

Actions

MST在安全模式下不能直接修改属性的值,必须通过action, 否则会报错

const Article = types .model("Article", { name: types.string, )) .actions((self) => ({ setName: (value) => { self.name = value; }, })); const article = Article.create({ name: "测试" }) article.setName("123");

可以通过unprotect解除安全模式,解除后可能造成代码的不规范。

unprotect(Article); article.name = "123";

生命周期

MST在提供了一些特殊的actions作为生命周期钩子

  • afterCreate: 实例创建成功后
  • afterAttach:子节点
  • beforeDetach
  • beforeDestroy
  • ....
const Article = types .model("Article", { // .... }) .actions((self) => ({ afterCreate: () => { console.log(self); } }));

快照

快照是状态树中特定时间点、固定的、序列化的对像,不包含类型信息,同时不可变。

在数据修改前可以保存数据的原始状态,然后根据快照来恢复状态,可以在表单修改前做一个快照,重置的时候直接从快照获取,避免再次请求后端接口。

常用方法:

  • getSnapshot(model): 返回当前model的快照
  • applySnapshot(model, snapshot): 使用快照更新model的状态
  • onSnapshot(model, snapshot): 用来监听是否有新快照可用,自动生成快照。
import { getSnapshot, applySnapshot } from "mobx-state-tree"; const Model = types.model("Article", { // .... }); const inst = Model.create(...); // 快照 let snapshot = getSnapshot(inst); // 恢复 applySnapshot(model, snapshot);

通过快照可以自动比较变更过的字段,实现数据的增量提交。

PS:snapshot中数据的类型需要跟props定义的类型相同。

Volatile state

在MST中props定义了状态的类型,数据必须跟状态相匹配,并且数据可以导出为标准的JSON对象。如果无法预知数据的结构或类型,可以使用 volatile

import { types } from "mobx-state-tree"; import { autorun } from "mobx"; const Article = types .model({}) .volatile((self) => ({ localState: 1 })) .actions((self) => ({ setX(value) { self.localState = value; } })); const article = Article.create(); autorun(() => console.log(article.localState)); article.setX({ name: "lisi" });

当修改数据的时候localState的值被打印出来了,说明localState也是Observable的。

类型校验原理

MST类型校验的原理概括就是数据要发生改变时校验新值的类型是否匹配,不匹配就报错。

下面以types.integer类型为例,代码如下

export const integer: ISimpleType<number> = new CoreType<number, number, number>( "integer", TypeFlags.Integer, (v) => isInteger(v) )

integer是通过 CoreType创建的,其中有一个包含:create、instantiate、validate、isValidSnapshot等方法。

执行create方法时,会调用 instantiate 方法创建所有子节点(ObjectNode),每一种类型都会实现instantiate ,并且在validate方法中调用isValidSnapshot方法,

isValidSnapshot(value: C, context: IValidationContext): IValidationResult { if (isPrimitive(value) && this.checker(value as any)) { return typeCheckSuccess() } const typeName = this.name === "Date" ? "Date or a unix milliseconds timestamp" : this.name return typeCheckFailure(context, value, `Value is not a ${typeName}`) }

这里的checker就是上面传入的 (v) => isInteger(v)

model中会对实例添加拦截器(willChange),在子节点的value发生改变前检查类型是否正确,其中typecheckInternal最终会调用到type.validate方法,最后调用isInteger方法

private willChange(chg: IObjectWillChange): IObjectWillChange | null { const change = chg as IObjectWillChange & { newValue?: any } const node = getStateTreeNode(change.object) const subpath = change.name as string node.assertWritable({ subpath }) const childType = (node.type as this).properties[subpath] // only properties are typed, state are stored as-is references if (childType) { typecheckInternal(childType, change.newValue) // .... } return change }