****MST(mobx-state-tree)、****redux做多组件间全局state管理(类比vuex,父 孙组件状态传递解耦)。
tree = type + state
树中的每个节点都由两件事来描述: type (事物的形状) 和 data (它当前所处的状态).
最简单的树如下所示:
1. 声明类型为` Root` 的节点的形状`。 model是一个types中最重要的一个type,使用types.model方法得到的就是Model,在Model中,可以包含多个type或者其他Model。
//定义type
const Root = types.model("Root", { str: types.string }); //定义模型形状(name为Root,props为{ str: types.string })
2.基于" Root "类型创建一个具有初始 状态和env 的树 。 通过 树可 执行action等 。Model.create可以传入两个参数,第一个是Model的初始状态值,第二个参数是可选参数,表示需要给 Model 及子 Model ****的env对象(****环境配置对象),env用于实现简单的依赖注入功能。
//创建model实例
const root = Root.create({str: " test " } , env ); // 创建model , 传入初始状态值 和 env环境配置对象参数 。
root.setStr("mst");//调用action
observer 函数/装饰器可以用来将 React 组件转变成响应式组件 。 它用 mobx.autorun 包装了组件的 render 函数以确保任何组件渲染中使用的数据变化时都可以强制刷新组件。 observer 是由单独的 mobx-react 包提供的。@observer 只会增强你正在装饰的组件,而不是内部使用了的组件。 所以通常你的所有组件都应该是装饰了的。但别担心,这样不会降低效率,相反 observer 组件越多,渲染效率越高。
当 observer 需要组合其它装饰器或高阶组件时,请确保 observer 是最深处(第一个应用)的装饰器,否则它可能什么都不做。
实现UI组件尽可能地使用observer装饰组件
使用mobx-react包提供的observer装饰器装饰后的组件能响应observable的变化,并做了诸多的性能优化,尽可能为你的组件加上observer,除非你想要自定义shouldComponentUpdate来控制组件更新时机。
React. shouldComponentUpdate() 方法会返回一个布尔值,指定 React 是否应该继续渲染,默认值是 true, 即 state 每次发生变化组件都会重新渲染。
@observer
export class TableContent extends React.Component<TableContentProps> { }
尽可能地编写无状态组件
有的同学可能看到过类似这样的说法: 用Redux管理全局状态,用组件State管理局部状态。
笔者不认同这种说法,根据笔者的经验来看,当项目复杂到一定的程度,使用State管理的状态会难受到让你抓狂:某个深层次的组件State只能通过改变上层组件传递的props来进行更新。
更何况,现在无状态(state less)组件越来越受到大家的认可,react hooks的出现也顺应了组件无状态的这个发展趋势。
当应用都由无状态组件构成,应用的状态都存储在触手可及的地方(如Redux或MST),想要在某些时刻修改某个状态值就变得轻而易举。
types
MST提供的一个重要对象就是types ,在这个对象中,包含了基元类型 (primitives types),如string、boolean、number ,还包含了一些复杂类型 的工厂方法和工具方法,常用的有model、array、map、optional等。
MST可用的类型和类型方法非常多,这里主要介绍model类型,其他的可以在这里查看:类型概述 ·MobX状态树 (mobx-state-tree.js.org)
model是一个types中最重要的一个type,使用types.model方法得到的就是Model,在Model中,可以包含多个type或者其他Model。
一个Model可以看作是一个节点(Node),节点之间相互组合,就构造出了整棵状态树(State Tree)。
types.model 创建一个可链接的 model 类型,其中每个链接方法都会生成一个新类型 :
.named(name)克隆当前类型,但为其指定一个新名称
.props(props)基于当前类型生成新类型,并添加/重写指定的属性
.actions(self => object literal with actions)基于当前类型生成一个新类型,并添加/覆盖指定的操作
.views(self => object literal with view functions)基于当前类型生成新类型,并添加/覆盖指定的视图函数
.preProcessSnapshot(snapshot => snapshot)可用于在实例化新模型之前对原始 JSON 进行预处理。
.postProcessSnapshot(snapshot => snapshot)可用于在获取模型快照之前对原始 JSON 进行后处理。
props
props指的是Model中的属性定义。props定义了这个Model维护的状态对象包含哪些字段,各字段对应的又是什么类型。
props中value可以是:
1.一种类型(可以是一个简单的基元类型 或者 复杂类型,也可以是一个预定义类型):
export const Test= types.model("name", {price: types.number});//第一个参数为name属性定义,第二个参数为props属性定义
//等同于
export const Test= types.model("name").props({price: types.number})
//预定义类型Todo
const Test= types.model("name").props({todos: types.array(Todo)})
2.基元
//使用基元作为类型 是 引入具有默认值的属性的语法糖(基元类型是从默认值推断出来的)
const Test= types.model("name").props({endpoint: "http://localhost"}) //等价于endpoint: types.optional(types.string, "http://localhost")
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=> ({
getpriceAfterDiscount () {
return self.price - self.discount;
}
}));
const productItem= ProductItem.create({price: 1});
productItem.priceAfterDiscount();
上面代码中,定义了priceAfterDiscount,表示商品的折后价格。调用.views方法时,传入的是一个方法,方法的参数self是当前Model的实例,方法需要返回一个对象,表示Model的views集合。
需要注意的是,定义views时有两种选择,使用getter或者不使用****。使用getter时,衍生数据的值会被缓存**** 直到依赖的数据发送变化。而不使用时,需要通过方法调用的方式获取衍生数据,无法对计算结果进行缓存****。尽可能使用getter,有助于提升应用的性能****
actions
actions是用于更新状态的方法集合。在安全模式下,所有对状态的更新操作必须在actions中执行,否则会报错
const Root = types
.model("Root", {
str: types.string,
})
.actions(self => ({
setStr (val: string) {
self.str = val;
},
afterCreate () {
// 执行一些初始化操作
}
}));
const root = Root.create({str: " test " });
root.setStr("mst");
除了通常意义上用来更新状态的actions外,在model.actions方法中,还可以设置一些特殊的actions:
afterCreate
afterAttach
beforeDetach
beforeDestroy
从名字上可以看出来,上面四位都是生命周期方法,可以使用他们在Model的各个生命周期执行一些操作
异步Action
异步更新状态是非常常见的需求,MST从底层支持异步action。
const model = types
.model(...)
.actions(self => ({
async getData () { // async/await
try {
const data = await api.getData();
} catch (err) {
}
},
// promise
updateData () {
return api.updateData()
.then(...)
.catch(...);
}
}));
若使用Promise、async/await来编写异步Action,在异步操作之后更新状态时,代码执行的上下文会脱离action,导致状态在action之外被更新而报错。解决办法:编写一个runInAction的action将更新状态的操作 放到runInAction中执行 。
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;
});
}
}));
但是在某些情况下,这种方法不够完美:一个异步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方法获取快照:applySnapshot方法可以更新Model的状态.
import { getSnapshot, applySnapshot } from "mobx-state-tree";
cosnt Model = types.model(...);
const model = Model.create(...);
console.log(getSnapshot(model));
applySnapshot(model, {
msg: "hello"
});
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。