React 之 mobx-state-tree(Redux替代品) 状态管理

****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

相关推荐
雪碧聊技术3 分钟前
01-Ajax入门与axios使用、URL知识
前端·javascript·ajax·url·axios库
adminIvan7 分钟前
Element plus使用menu时候如何在折叠时候隐藏掉组件自带的小箭头
前端·javascript·vue.js
会发光的猪。26 分钟前
【 ElementUI 组件Steps 步骤条使用新手详细教程】
前端·javascript·vue.js·elementui·前端框架
我家媳妇儿萌哒哒27 分钟前
el-table合并单元格之后,再进行隔行换色的且覆盖表格行鼠标移入的背景色的实现
前端·javascript·elementui
baiduguoyun42 分钟前
react的import 导入语句中的特殊符号
前端·react.js
前端青山44 分钟前
webpack指南
开发语言·前端·javascript·webpack·前端框架
NiNg_1_2341 小时前
ECharts实现数据可视化入门详解
前端·信息可视化·echarts
励志前端小黑哥2 小时前
有了Miniconda,再也不用担心nodejs、python、go的版本问题了
前端·python
喵叔哟2 小时前
重构代码之取消临时字段
java·前端·重构
还是大剑师兰特2 小时前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts