[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。

喜歡本文歡迎關注和收藏,轉載請註明出處,謝謝支持。

相關文章
相關標籤/搜索