?系列文章目录?
预备知识
在正式进入主题前,你需要确认一下,是否已经掌握下面几个工具和库的使用:
- MobX:这是MST的核心,MST中存储的响应式“状态”都是
MobX
的Observable
- React:使用
React
来测试MST的功能非常简单 - TypeScript:后文中会使用TS来编写示例代码,TS强大的智能提示和类型检查,有助于快速掌握MST的API
上面列举的工具和库都有非常丰富的文档和教程,不太熟悉的同学最好先自学一下。
安装
MST依赖MobX。
项目中执行yarn add mobx mobx-state-tree
即可完成安装。
MobX有两个版本,新版本需要浏览器Proxy支持,一些老旧的浏览器并不支持,需要兼容老浏览器的请安装mobx@4:yarn add mobx@4 mobx-state-tree
。
Type、Model
使用MST来维护状态,首先需要让MST知道,这个状态的结构是什么样的。
MST内建了一个类型机制。通过类型的组合就可以定义出整个状态的形状。
并且,在开发环境下,MST可以通过这个定义好的形状,来判断状态的值和形状与其对应的类型是否匹配,确保状态的类型与预期一致,这有助于在开发时及时发现数据类型的问题:
MST提供的一个重要对象就是types
,在这个对象中,包含了基础的元类型
(primitives types),如string
、boolean
、number
,还包含了一些复杂类型的工厂方法和工具方法,常用的有model
、array
、map
、optional
等。
model
是一个types
中最重要的一个type,使用types.model
方法得到的就是Model
,在Model
中,可以包含多个type或者其他Model
。
一个Model
可以看作是一个节点(Node),节点之间相互组合,就构造出了整棵状态树(State Tree)。
MST可用的类型和类型方法非常多,这里不一一列举,可以在这里查看完整的列表。
完成Model
的定义后,可以使用Model.create
方法获得Model
的实例。Model.create
可以传入两个参数,第一个是Model
的初始状态值,第二个参数是可选参数,表示需要给Model
及子Model
的env对象(环境配置对象),env用于实现简单的依赖注入
功能,在后续文章中再详细说明。
Props
props指的是Model
中的属性定义。props定义了这个Model
维护的状态对象包含哪些字段,各字段对应的又是什么类型。
拿开篇中的“商品”作为例子:
import { types } from 'mobx-state-tree';
export const ProductItem = types.model('ProductItem', {
prodName: types.string,
price: types.number,
});
复制代码
types.model
方法的第一个参数为Model
设定了名称,第二个参数传入了一个对象,这个对象就是Model
的props。
上面代码中,指定了ProductItem
这个Model
包含了类型为string
的prodName
属性和类型为number
的price
属性。
注意,可以省略types.model
的第二个参数,然后使用model.props
方法来定义props。
export const ProductItem = types
.model('ProductItem')
.props({
prodName: types.string,
price: types.number,
});
复制代码
上面的两份代码得到的ProductItem
是相同的(实际上有一些细微差别,但可以完全忽略)。
定义了props之后,在Model的实例上可以访问到相应的字段:
const productItem = ProductItem.create({prodName: '商品标题xxx', price: 99.9});
console.log(productItem.prodName); // 商品标题xxx
console.log(productItem.price); // 99.9
复制代码
Views
views是Model
中一系列衍生数据
或获取衍生数据的方法
的集合,类似Vue组件的computed计算属性。
在定义Model
时,可以使用model.views
方法定义views。
export const ProductItem = types
.model('ProductItem', {
prodName: types.string,
price: types.number,
discount: types.number,
})
.views(self => ({
get priceAfterDiscount () {
return self.price - self.discount;
}
}));
复制代码
上面代码中,定义了priceAfterDiscount
,表示商品的折后价格。调用.views
方法时,传入的是一个方法,方法的参数self
是当前Model
的实例,方法需要返回一个对象,表示Model
的views集合。
需要注意的是,定义views时有两种选择,使用getter
或者不使用。使用getter
时,衍生数据的值会被缓存直到依赖的数据发送变化。而不使用时,需要通过方法调用的方式获取衍生数据,无法对计算结果进行缓存。尽可能使用getter
,有助于提升应用的性能。
Actions
actions是用于更新状态的方法集合。
在创建Model
时,使用model.actions
方法来定义actions:
const Root = types
.model('Root', {
str: types.string,
})
.actions(self => ({
setStr (val: string) {
self.str = val;
}
}));
const root = Root.create({str: 'mobx'});
root.setStr('mst');
复制代码
在安全模式下,所有对状态的更新操作必须在actions中执行,否则会报错:
可以使用unprotect
方法解除安全模式(不推荐):
import { types, unprotect } from 'mobx-state-tree';
const Root = types.model(...);
unprotect(Root);
root.str = 'mst'; // ok
复制代码
除了通常意义上用来更新状态的actions外,在model.actions
方法中,还可以设置一些特殊的actions:
- afterCreate
- afterAttach
- beforeDetach
- beforeDestroy
从名字上可以看出来,上面四位都是生命周期方法
,可以使用他们在Model
的各个生命周期执行一些操作:
const Model = types
.model(...)
.actions(self => ({
afterCreate () {
// 执行一些初始化操作
}
}));
复制代码
具体的MST生命周期在后续文章中再详细讨论。
异步Action、Flow
异步更新状态是非常常见的需求,MST从底层支持异步action。
const model = types
.model(...)
.actions(self => ({
// async/await
async getData () {
try {
const data = await api.getData();
...
} catch (err) {
...
}
...
},
// promise
updateData () {
return api.updateData()
.then(...)
.catch(...);
}
}));
复制代码
需要注意,上文提到过:
在安全模式下,所有对状态的更新操作必须在actions中执行,否则会报错
若使用Promise、async/await来编写异步Action,在异步操作之后更新状态时,代码执行的上下文会脱离action,导致状态在action之外被更新而报错。这里有两种解决办法:
- 将更新状态的操作单独封装成action
- 编写一个
runInAction
的action在异步操作中使用
// 方法1
const Model = types
.model(...)
.actions(self => ({
setLoading (loading: boolean) {
self.loading = loading;
},
setData (data: any) {
self.data = data;
},
async getData () {
...
self.setLoading(true); // 这里因为在异步操作之前,直接赋值self.loading = true也ok
const data = await api.getData();
self.setData(data);
self.setLoading(false);
...
}
}));
// 方法2
const Model = types
.model(...)
.actions(self => ({
runInAction (fn: () => any) {
fn();
},
async getData () {
...
self.runInAction(() => self.loading = true);
const data = await api.getData();
self.runInAction(() => {
self.data = data;
self.loading = false;
});
...
}
}));
复制代码
方法1需要额外封装N个action,比较麻烦。方法2封装一次就可以多次使用。
但是在某些情况下,两种方法都不够完美:一个异步action被分割成了N个action调用,无法使用MST的插件机制实现整个异步action的原子操作、撤销/重做等高级功能。
为了解决这个问题,MST提供了flow
方法来创建异步action:
import { types, flow } from 'mobx-state-tree';
const model = types
.model(...)
.actions(self => {
const getData = flow(function * () {
self.loading = true;
try {
const data = yield api.getData();
self.data = data;
} catch (err) {
...
}
self.loading = false;
});
return {
getData
};
})
复制代码
使用flow
方法需要传入一个generator function
,在这个生成器方法中,使用yield
关键字可以resolve异步操作。并且,在方法中可以直接给状态赋值,写起来更简单自然。
Snapshot
snapshot即“快照”,表示某一时刻,Model
的状态序列化之后的值。这个值是标准的JS对象。
使用getSnapshot
方法获取快照:
import { getSnapshot } from 'mobx-state-tree';
cosnt Model = types.model(...);
const model = Model.create(...);
console.log(getSnapshot(model));
复制代码
使用applySnapshot
方法可以更新Model
的状态:
import { applySnapshot } from 'mobx-state-tree';
...
applySnapshot(model, {
msg: 'hello'
});
复制代码
通过applySnapshot
方法更新状态时,传入的状态值必须匹配Model
的类型定义,否则会报错:
getSnapshot
及applySnapshot
方法都可以用在Model
的子Model
上使用。
Volatile State
在MST中,props对应的状态都是可持久化
的,也就是可以序列化为标准的JSON数据。并且,props对应的状态必须与props的类型相匹配。
如果需要在Model中存储无需持久化,并且数据结构或类型无法预知的动态数据,可以设置为Volatile State
。
Volatile State
使用model.volatile
方法定义:
import { types } from 'mobx-state-tree';
import { autorun } from 'mobx';
const Model = types
.model('Model')
.volatile(self => ({
anyData: {} as any
}))
.actions(self => ({
runInAction (fn: () => any) {
fn();
}
}));
const model = Model.create();
autorun(() => console.log(model.anyData));
model.runInAction(() => {
model.anyData = {a: 1};
});
model.runInAction(() => {
model.anyData.a = 2;
});
复制代码
和actions及views一样,model.volatile
方法也要传入一个参数为Model
实例的方法,并返回一个对象。
运行上面代码,可得到如下输出:
代码中使用Mobx的autorun
方法监听并打印model.anyData
的值,图中一共看到2次输出:
- anyData的初始值
- 第一次更新anyData后的值
但是第二次为anyData.a
赋值并没有执行autorun。
由此可见,Volatile State
的值也是Observable
,但是只会响应引用的变化,是一个非Deep Observable
。
volatile demo代码
可以点开上面的链接,修改其中的代码,熟悉一下上面提到的几个方法的使用。
小结
本章介绍了MST的基础概念和重要的几个API,后面会给大家讲解使用MST搭配React来实现一个完整的Todo List
demo。
喜欢本文欢迎关注和收藏,转载请注明出处,谢谢支持。