在正式進入主題前,你須要確認一下,是否已經掌握下面幾個工具和庫的使用:git
MobX
的Observable
React
來測試MST的功能很是簡單上面列舉的工具和庫都有很是豐富的文檔和教程,不太熟悉的同窗最好先自學一下。github
MST依賴MobX。api
項目中執行yarn add mobx mobx-state-tree
便可完成安裝。promise
MobX有兩個版本,新版本須要瀏覽器Proxy支持,一些老舊的瀏覽器並不支持,須要兼容老瀏覽器的請安裝mobx@4:yarn add mobx@4 mobx-state-tree
。瀏覽器
使用MST來維護狀態,首先須要讓MST知道,這個狀態的結構是什麼樣的。緩存
MST內建了一個類型機制。經過類型的組合就能夠定義出整個狀態的形狀。安全
而且,在開發環境下,MST能夠經過這個定義好的形狀,來判斷狀態的值和形狀與其對應的類型是否匹配,確保狀態的類型與預期一致,這有助於在開發時及時發現數據類型的問題:數據結構
MST提供的一個重要對象就是types
,在這個對象中,包含了基礎的元類型
(primitives types),如string
、boolean
、number
,還包含了一些複雜類型的工廠方法和工具方法,經常使用的有model
、array
、map
、optional
等。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指的是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是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是用於更新狀態的方法集合。
在建立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:
從名字上能夠看出來,上面四位都是生命週期方法
,可使用他們在Model
的各個生命週期執行一些操做:
const Model = types
.model(...)
.actions(self => ({
afterCreate () {
// 執行一些初始化操做
}
}));
複製代碼
具體的MST生命週期在後續文章中再詳細討論。
異步更新狀態是很是常見的需求,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以外被更新而報錯。這裏有兩種解決辦法:
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即「快照」,表示某一時刻,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
上使用。
在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.a
賦值並無執行autorun。
因而可知,Volatile State
的值也是Observable
,可是隻會響應引用的變化,是一個非Deep Observable
。
能夠點開上面的連接,修改其中的代碼,熟悉一下上面提到的幾個方法的使用。
本章介紹了MST的基礎概念和重要的幾個API,後面會給你們講解使用MST搭配React來實現一個完整的Todo List
demo。
喜歡本文歡迎關注和收藏,轉載請註明出處,謝謝支持。