定義Model時,須要正確地定義props中各字段的類型。本文將對MST提供的各類類型以及類型的工廠方法進行簡單的介紹,方便同窗們在定義props時挑選正確的類型。react
定義props以前,有一個前提是,你已經明確地知道這個Model中狀態的數據類型。後端
若是Model用於存放由後端API返回的數據,那麼必定要和後端確認返回值在全部狀況下的類型。好比,某個字段在沒有值的時候你覺得會給一個''
,然後端卻給了個null
;或者某個數組你覺得會給你一個空數組[]
,他又甩你一個null
。若是你不打算本身編寫一個標準化數據的方法,那必定要和數據的提供方肯定這些細節。數組
定義一個字符串類型字段。安全
定義一個數值類型字段。app
定義一個布爾類型字段。ide
定義一個整數類型字段。post
注意,即便是TypeScript中也沒有「整數」這個類型,在編碼時,傳入一個帶小數的值TypeScript也沒法發現其中的類型錯誤。如無必要,請使用types.number
。ui
定義一個日期類型字段。編碼
這個類型存儲的值是標準的Date對象。在設置值時,能夠選擇傳入數值類型的時間戳或者Date對象。spa
export const Model = types
.model({
date: types.Date
})
.actions(self => ({
setDate (val: Date | number) {
self.date = date;
}
}));
複製代碼
定義一個值爲null
的類型字段。
定義一個值爲undefined
的類型字段。
定義一個對象類型的字段。
定義一個數組類型的字段。
types.array(types.string);
複製代碼
上面的代碼定義了一個字符串數組的類型。
定義一個map類型的字段。該map的key都爲字符串類型,map的值都爲指定類型。
types.map(types.number);
複製代碼
根據傳入的參數,定義一個帶有默認值的可選類型。
types.optional
是一個方法,方法有兩個參數,第一個參數是數據的真實類型,第二個參數是數據的默認值。
types.optional(types.number, 1);
複製代碼
上面的代碼定義了一個默認值爲1的數值類型。
注意,types.array
或者types.map
定義的類型自帶默認值(array爲[]
,map爲{}
),也就是說,下面兩種定義的結果是同樣的:
// 使用types.optional
types.optional(types.array(types.number), []);
types.optional(types.map(types.number), {});
// 不使用types.optional
types.array(types.number);
types.map(types.number);
複製代碼
若是要設置的默認值與types.array
或types.map
自帶的默認值相同,那麼就不須要使用types.optional
。
若是想控制類型更底層的如序列化和反序列化、類型校驗等細節,或者根據一個class或interface來定義類型,可使用types.custom
定義自定義類型。
class Decimal {
...
}
const DecimalPrimitive = types.custom<string, Decimal>({
name: "Decimal",
fromSnapshot(value: string) {
return new Decimal(value)
},
toSnapshot(value: Decimal) {
return value.toString()
},
isTargetType(value: string | Decimal): boolean {
return value instanceof Decimal
},
getValidationMessage(value: string): string {
if (/^-?\d+\.\d+$/.test(value)) return "" // OK
return `'${value}' doesn't look like a valid decimal number`
}
});
複製代碼
上面的代碼定義了一個Decimal類型。
實際開發中也許會遇到這樣的狀況:一個值的類型多是字符串,也多是數值。那咱們就可使用types.union
定義聯合類型:
types.union(types.number, types.string);
複製代碼
聯合類型能夠有任意個聯合的類型。
字面值類型能夠限制存儲的內容與給定的值嚴格相等。
好比使用types.literal('male')
定義的狀態值只能爲'male'
。
實際上,上面提到過的types.null
以及types.undefined
就是字面值類型:
const NullType = types.literal(null);
const UndefinedType = types.literal(undefined);
複製代碼
搭配聯合類型,能夠這樣定義一個性別
類型:
const GenderType = types.union(types.literal('male'), types.literal('female'));
複製代碼
枚舉類型能夠看做是聯合類型以及字面值類型的一層封裝,好比上面的性別
可使用枚舉類型來定義:
const GenderType = types.enumeration('Gender', ['male', 'female']);
複製代碼
方法的第一個參數是可選的,表示枚舉類型的名稱。第二個參數傳入的是字面值數組。
在TypeScript環境下,能夠這樣搭配TypeScript枚舉使用:
enum Gender {
male,
female
}
const GenderType = types.enumeration<Gender>('Gender', Object.values(Gender));
複製代碼
定義一個可能爲undefined
的字段,並自帶默認值undefined
。
types.maybe(type)
// 等同於
types.optional(types.union(type, types.literal(undefined)), undefined)
複製代碼
與types.maybe
相似,將undefined
替換成了null
。
types.maybeNull(type)
// 等同於
types.optional(types.union(type, types.literal(null)), null)
複製代碼
frozen
意爲「凍結的」,types.frozen
方法用來定義一個immutable類型,而且存放的值必須是可序列化的。
當數據的類型不肯定時,在TypeScript中一般將值的類型設置爲any
,而在MST中,就須要使用types.frozen
定義。
const Model = types
.model('Model', {
anyData: types.frozen()
})
.actions(self => ({
setAnyData (data: any) {
self.anyData = data;
}
}));
複製代碼
在MST看來,使用types.frozen
定義類型的狀態值是不可變的,因此會出現這樣的狀況:
model.anyData = {a: 1, b: 2}; // ok, reactive
model.anyData.b = 3; // not reactive
複製代碼
也就是只有設置一個新的值給這個字段,相關的observer纔會響應狀態的更新。而修改這個字段內部的某個值,是不會被捕捉到的。
有時候會出現這樣的需求,須要一個Model A,在A中,存在類型爲A自己的字段。
若是這樣寫:
const A = types
.model('A', {
a: types.maybe(A), // 使用mabe避免無限循環
});
複製代碼
會提示Block-scoped variable 'A' used before its declaration
,也就是在A定義完成以前就試圖使用他,這樣是不被容許的。
這個時候就須要使用types.late
:
const A = types
.model('A', {
a: types.maybe(types.late(() => A))
});
複製代碼
types.late
須要傳入一個方法,在方法中返回A
,這樣就能夠避開上面報錯的問題。
types.refinement
能夠在其餘類型的基礎上,添加額外的類型校驗規則。
好比須要定義一個email字段,類型爲字符串但必須知足email的標準格式,就能夠這樣作:
const EmailType = types.refinement(
'Email',
types.string,
(snapshot) => /^[a-zA-Z_1-9]+@\.[a-z]+/.test(snapshot), // 校驗是否符合email格式
);
複製代碼
拿上一篇文章中的TodoList做爲例子,咱們在對Todo列表中的某一個Todo進行編輯的時候,須要經過id跟蹤這個Todo,在提交編輯結果時,經過這個id找到對應的Todo對象,而後進行更新。
這種須要跟蹤、查找的需求很常見,寫多了也以爲麻煩。
好在MST提供了一個優雅的解決方案:引用類型和標識類型。
這二者須要搭配使用才能發揮做用:
標識就是數據對象的惟一標識字段,這個字段的值在庫中保持惟一,也就是primary_key。
好比上一篇文章中的TodoItem,能夠改造爲:
export const TodoItem = types
.model('TodoItem', {
id: types.identifier,
title: types.string,
done: types.boolean,
});
複製代碼
改造TodoList:
export const TodoList = types
.model('TodoList', {
...
list: types.array(TodoItem),
editTarget: types.reference(TodoItem),
...
});
複製代碼
而後在建立Model實例,或者applySnapshot的時候,能夠將editTarget
的值設定爲正在編輯的TodoItem的id值,MST就會自動在list中查找id相同的TodoItem:
const todoList = TodoList.create({
list: [
{id: '1', title: 'Todo 1', done: true},
{id: '2', title: 'Todo 2', done: true},
...
],
editTarget: '1'
});
//此時的editTarget就是list中id爲'1'的TodoItem對象
console.log(todoList.list[0] === todoList.editTarget); // true
todoList.editTarget = todoItem2; // todoItem2爲id爲'2'的TodoItem對象
console.log(getSnapshot(todoList).editTarget === '2'); // true
todoList.editTarget = '2' as any;
console.log(getSnapshot(todoList).editTarget === '2'); // true
複製代碼
上面的代碼說明,reference類型的字段本質上維護的是目標的標識字段值,而且,除了將目標對象賦值給reference字段外,將目標標識字段值賦值給reference字段的效果是同樣的。
另外,reference不只僅能搭配array使用,也能在map中查找:
const TodoList = types.model('TodoList', {
todoMap: types.map(TodoItem),
editTarget: types.reference(TodoItem)
});
複製代碼
甚至,MST也容許你自定義查找器(resolver),給types.reference
指定第二個參數,好比官網的這個例子:
const User = types.model({
id: types.identifier,
name: types.string
})
const UserByNameReference = types.maybeNull(
types.reference(User, {
// given an identifier, find the user
get(identifier /* string */, parent: any /*Store*/) {
return parent.users.find(u => u.name === identifier) || null
},
// given a user, produce the identifier that should be stored
set(value /* User */) {
return value.name
}
})
)
const Store = types.model({
users: types.array(User),
selection: UserByNameReference
})
const s = Store.create({
users: [{ id: "1", name: "Michel" }, { id: "2", name: "Mattia" }],
selection: "Mattia"
})
複製代碼
若對象的惟一標識字段的值爲數值類型,那麼可使用types.identifierNumber
代替types.identifier
。
這是一個「安全」的引用類型:
const Todo = types.model({ id: types.identifier })
const Store = types.model({
todos: types.array(Todo),
selectedTodo: types.safeReference(Todo)
});
複製代碼
當selectedTodo
引用的目標從todos
這個節點被移除後,selectedTodo
會自動被設置爲undefined
。
MST提供的類型和類型方法很是齊全,利用好他們就能爲任意數據定義恰當的類型。
喜歡本文的歡迎關注+收藏,轉載請註明出處,謝謝支持。