?系列文章目錄?
預備知識
在正式進入主題前,你需要確認一下,是否已經掌握下面幾個工具和庫的使用:
- MobX:這是MST的核心,MST中儲存的響應式“狀態”都是
MobX
的Observable
- React:使用
React
來測試MST的功能非常簡單 - TypeScript:後文中會使用TS來編寫示例程式碼,TS強大的智慧提示和型別檢查,有助於快速掌握MST的API
上面列舉的工具和庫都有非常豐富的文件和教程,不太熟悉的同學最好先自學一下。
安裝
MST依賴MobX。
專案中執行yarn add mobx mobx-state-tree
即可完成安裝。
MobX有兩個版本,新版本需要瀏覽器Proxy支援,一些老舊的瀏覽器並不支援,需要相容老瀏覽器的請安裝mobx@4:yarn add mobx@4 mobx-state-tree
。
Type、Model
使用MST來維護狀態,首先需要讓MST知道,這個狀態的結構是什麼樣的。
MST內建了一個型別機制。通過型別的組合就可以定義出整個狀態的形狀。
並且,在開發環境下,MST可以通過這個定義好的形狀,來判斷狀態的值和形狀與其對應的型別是否匹配,確保狀態的型別與預期一致,這有助於在開發時及時發現資料型別的問題:
MST提供的一個重要物件就是types
,在這個物件中,包含了基礎的元型別
(primitives types),如string
、boolean
、number
,還包含了一些複雜型別的工廠方法和工具方法,常用的有model
、array
、map
、optional
等。
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
包含了型別為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
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中執行,否則會報錯:
可以使用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之外被更新而報錯。這裡有兩種解決辦法:
- 將更新狀態的操作單獨封裝成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
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
上使用。
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
。
可以點開上面的連結,修改其中的程式碼,熟悉一下上面提到的幾個方法的使用。
小結
本章介紹了MST的基礎概念和重要的幾個API,後面會給大家講解使用MST搭配React來實現一個完整的Todo List
demo。
喜歡本文歡迎關注和收藏,轉載請註明出處,謝謝支援。