[MobX State Tree数据组件化开发][1]:MST基础

👉系列文章目录👈

预备知识

在正式进入主题前,你须要确认一下,是否已经掌握下面几个工具和库的使用:git

  • MobX:这是MST的核心,MST中存储的响应式“状态”都是MobXObservable
  • React:使用React来测试MST的功能很是简单
  • TypeScript:后文中会使用TS来编写示例代码,TS强大的智能提示和类型检查,有助于快速掌握MST的API

上面列举的工具和库都有很是丰富的文档和教程,不太熟悉的同窗最好先自学一下。github

安装

MST依赖MobX。api

项目中执行yarn add mobx mobx-state-tree便可完成安装。promise

MobX有两个版本,新版本须要浏览器Proxy支持,一些老旧的浏览器并不支持,须要兼容老浏览器的请安装mobx@4:yarn add mobx@4 mobx-state-tree浏览器

Type、Model

使用MST来维护状态,首先须要让MST知道,这个状态的结构是什么样的。缓存

MST内建了一个类型机制。经过类型的组合就能够定义出整个状态的形状。安全

而且,在开发环境下,MST能够经过这个定义好的形状,来判断状态的值和形状与其对应的类型是否匹配,确保状态的类型与预期一致,这有助于在开发时及时发现数据类型的问题:数据结构

MST类型检查

MST提供的一个重要对象就是types,在这个对象中,包含了基础的元类型(primitives types),如stringbooleannumber,还包含了一些复杂类型的工厂方法和工具方法,经常使用的有modelarraymapoptional等。app

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包含了类型为stringprodName属性和类型为numberprice属性。

注意,能够省略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中执行,不然会报错:

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以外被更新而报错。这里有两种解决办法:

  1. 将更新状态的操做单独封装成action
  2. 编写一个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的类型定义,不然会报错:

getSnapshotapplySnapshot方法均可以用在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次输出:

  1. anyData的初始值
  2. 第一次更新anyData后的值

可是第二次为anyData.a赋值并无执行autorun。

因而可知,Volatile State的值也是Observable,可是只会响应引用的变化,是一个非Deep Observable

volatile demo代码

能够点开上面的连接,修改其中的代码,熟悉一下上面提到的几个方法的使用。

小结

本章介绍了MST的基础概念和重要的几个API,后面会给你们讲解使用MST搭配React来实现一个完整的Todo Listdemo。

喜欢本文欢迎关注和收藏,转载请注明出处,谢谢支持。

相关文章
相关标签/搜索