[MobX State Tree数据组件化开发][3]:选择正确的types.xxx
?系列文章目錄?
定義Model時(shí),需要正確地定義props中各字段的類(lèi)型。本文將對(duì)MST提供的各種類(lèi)型以及類(lèi)型的工廠方法進(jìn)行簡(jiǎn)單的介紹,方便同學(xué)們?cè)诙xprops時(shí)挑選正確的類(lèi)型。
前提
定義props之前,有一個(gè)前提是,你已經(jīng)明確地知道這個(gè)Model中狀態(tài)的數(shù)據(jù)類(lèi)型。
如果Model用于存放由后端API返回的數(shù)據(jù),那么一定要和后端確認(rèn)返回值在所有情況下的類(lèi)型。比如,某個(gè)字段在沒(méi)有值的時(shí)候你以為會(huì)給一個(gè)'',而后端卻給了個(gè)null;或者某個(gè)數(shù)組你以為會(huì)給你一個(gè)空數(shù)組[],他又甩你一個(gè)null。如果你不打算自己編寫(xiě)一個(gè)標(biāo)準(zhǔn)化數(shù)據(jù)的方法,那一定要和數(shù)據(jù)的提供方確定這些細(xì)節(jié)。
基礎(chǔ)類(lèi)型
types.string
定義一個(gè)字符串類(lèi)型字段。
types.number
定義一個(gè)數(shù)值類(lèi)型字段。
types.boolean
定義一個(gè)布爾類(lèi)型字段。
types.integer
定義一個(gè)整數(shù)類(lèi)型字段。
注意,即使是TypeScript中也沒(méi)有“整數(shù)”這個(gè)類(lèi)型,在編碼時(shí),傳入一個(gè)帶小數(shù)的值TypeScript也無(wú)法發(fā)現(xiàn)其中的類(lèi)型錯(cuò)誤。如無(wú)必要,請(qǐng)使用types.number。
types.Date
定義一個(gè)日期類(lèi)型字段。
這個(gè)類(lèi)型存儲(chǔ)的值是標(biāo)準(zhǔn)的Date對(duì)象。在設(shè)置值時(shí),可以選擇傳入數(shù)值類(lèi)型的時(shí)間戳或者Date對(duì)象。
export const Model = types.model({date: types.Date }).actions(self => ({setDate (val: Date | number) {self.date = date;}})); 復(fù)制代碼types.null
定義一個(gè)值為null的類(lèi)型字段。
types.undefined
定義一個(gè)值為undefined的類(lèi)型字段。
復(fù)合類(lèi)型
types.model
定義一個(gè)對(duì)象類(lèi)型的字段。
types.array
定義一個(gè)數(shù)組類(lèi)型的字段。
types.array(types.string); 復(fù)制代碼上面的代碼定義了一個(gè)字符串?dāng)?shù)組的類(lèi)型。
types.map
定義一個(gè)map類(lèi)型的字段。該map的key都為字符串類(lèi)型,map的值都為指定類(lèi)型。
types.map(types.number); 復(fù)制代碼可選類(lèi)型,types.optional
根據(jù)傳入的參數(shù),定義一個(gè)帶有默認(rèn)值的可選類(lèi)型。
types.optional是一個(gè)方法,方法有兩個(gè)參數(shù),第一個(gè)參數(shù)是數(shù)據(jù)的真實(shí)類(lèi)型,第二個(gè)參數(shù)是數(shù)據(jù)的默認(rèn)值。
types.optional(types.number, 1); 復(fù)制代碼上面的代碼定義了一個(gè)默認(rèn)值為1的數(shù)值類(lèi)型。
注意,types.array或者types.map定義的類(lèi)型自帶默認(rèn)值(array為[],map為{}),也就是說(shuō),下面兩種定義的結(jié)果是一樣的:
// 使用types.optional types.optional(types.array(types.number), []); types.optional(types.map(types.number), {});// 不使用types.optional types.array(types.number); types.map(types.number); 復(fù)制代碼如果要設(shè)置的默認(rèn)值與types.array或types.map自帶的默認(rèn)值相同,那么就不需要使用types.optional。
自定義類(lèi)型,types.custom
如果想控制類(lèi)型更底層的如序列化和反序列化、類(lèi)型校驗(yàn)等細(xì)節(jié),或者根據(jù)一個(gè)class或interface來(lái)定義類(lèi)型,可以使用types.custom定義自定義類(lèi)型。
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 "" // OKreturn `'${value}' doesn't look like a valid decimal number`} }); 復(fù)制代碼上面的代碼定義了一個(gè)Decimal類(lèi)型。
聯(lián)合類(lèi)型,types.union
實(shí)際開(kāi)發(fā)中也許會(huì)遇到這樣的情況:一個(gè)值的類(lèi)型可能是字符串,也可能是數(shù)值。那我們就可以使用types.union定義聯(lián)合類(lèi)型:
types.union(types.number, types.string); 復(fù)制代碼聯(lián)合類(lèi)型可以有任意個(gè)聯(lián)合的類(lèi)型。
字面值類(lèi)型,types.literal
字面值類(lèi)型可以限制存儲(chǔ)的內(nèi)容與給定的值嚴(yán)格相等。
比如使用types.literal('male')定義的狀態(tài)值只能為'male'。
實(shí)際上,上面提到過(guò)的types.null以及types.undefined就是字面值類(lèi)型:
const NullType = types.literal(null); const UndefinedType = types.literal(undefined); 復(fù)制代碼搭配聯(lián)合類(lèi)型,可以這樣定義一個(gè)性別類(lèi)型:
const GenderType = types.union(types.literal('male'), types.literal('female')); 復(fù)制代碼枚舉類(lèi)型,types.enumeration
枚舉類(lèi)型可以看作是聯(lián)合類(lèi)型以及字面值類(lèi)型的一層封裝,比如上面的性別可以使用枚舉類(lèi)型來(lái)定義:
const GenderType = types.enumeration('Gender', ['male', 'female']); 復(fù)制代碼方法的第一個(gè)參數(shù)是可選的,表示枚舉類(lèi)型的名稱(chēng)。第二個(gè)參數(shù)傳入的是字面值數(shù)組。
在TypeScript環(huán)境下,可以這樣搭配TypeScript枚舉使用:
enum Gender {male,female }const GenderType = types.enumeration<Gender>('Gender', Object.values(Gender)); 復(fù)制代碼可undefined類(lèi)型,types.maybe
定義一個(gè)可能為undefined的字段,并自帶默認(rèn)值undefined。
types.maybe(type) // 等同于 types.optional(types.union(type, types.literal(undefined)), undefined) 復(fù)制代碼可空類(lèi)型,types.maybeNull
與types.maybe類(lèi)似,將undefined替換成了null。
types.maybeNull(type) // 等同于 types.optional(types.union(type, types.literal(null)), null) 復(fù)制代碼不可不類(lèi)型,types.frozen
frozen意為“凍結(jié)的”,types.frozen方法用來(lái)定義一個(gè)immutable類(lèi)型,并且存放的值必須是可序列化的。
當(dāng)數(shù)據(jù)的類(lèi)型不確定時(shí),在TypeScript中通常將值的類(lèi)型設(shè)置為any,而在MST中,就需要使用types.frozen定義。
const Model = types.model('Model', {anyData: types.frozen()}).actions(self => ({setAnyData (data: any) {self.anyData = data;}})); 復(fù)制代碼在MST看來(lái),使用types.frozen定義類(lèi)型的狀態(tài)值是不可變的,所以會(huì)出現(xiàn)這樣的情況:
model.anyData = {a: 1, b: 2}; // ok, reactive model.anyData.b = 3; // not reactive 復(fù)制代碼也就是只有設(shè)置一個(gè)新的值給這個(gè)字段,相關(guān)的observer才會(huì)響應(yīng)狀態(tài)的更新。而修改這個(gè)字段內(nèi)部的某個(gè)值,是不會(huì)被捕捉到的。
滯后類(lèi)型,types.late
有時(shí)候會(huì)出現(xiàn)這樣的需求,需要一個(gè)Model A,在A中,存在類(lèi)型為A本身的字段。
如果這樣寫(xiě):
const A = types.model('A', {a: types.maybe(A), // 使用mabe避免無(wú)限循環(huán)}); 復(fù)制代碼會(huì)提示Block-scoped variable 'A' used before its declaration,也就是在A定義完成之前就試圖使用他,這樣是不被允許的。
這個(gè)時(shí)候就需要使用types.late:
const A = types.model('A', {a: types.maybe(types.late(() => A))}); 復(fù)制代碼types.late需要傳入一個(gè)方法,在方法中返回A,這樣就可以避開(kāi)上面報(bào)錯(cuò)的問(wèn)題。
提純類(lèi)型,types.refinement
types.refinement可以在其他類(lèi)型的基礎(chǔ)上,添加額外的類(lèi)型校驗(yàn)規(guī)則。
比如需要定義一個(gè)email字段,類(lèi)型為字符串但必須滿足email的標(biāo)準(zhǔn)格式,就可以這樣做:
const EmailType = types.refinement('Email',types.string,(snapshot) => /^[a-zA-Z_1-9]+@\.[a-z]+/.test(snapshot), // 校驗(yàn)是否符合email格式 ); 復(fù)制代碼引用與標(biāo)識(shí)類(lèi)型
拿上一篇文章中的TodoList作為例子,我們?cè)趯?duì)Todo列表中的某一個(gè)Todo進(jìn)行編輯的時(shí)候,需要通過(guò)id跟蹤這個(gè)Todo,在提交編輯結(jié)果時(shí),通過(guò)這個(gè)id找到對(duì)應(yīng)的Todo對(duì)象,然后進(jìn)行更新。
這種需要跟蹤、查找的需求很常見(jiàn),寫(xiě)多了也覺(jué)得麻煩。
好在MST提供了一個(gè)優(yōu)雅的解決方案:引用類(lèi)型和標(biāo)識(shí)類(lèi)型。
這兩者需要搭配使用才能發(fā)揮作用:
定義標(biāo)識(shí),types.identifier
標(biāo)識(shí)就是數(shù)據(jù)對(duì)象的唯一標(biāo)識(shí)字段,這個(gè)字段的值在庫(kù)中保持唯一,也就是primary_key。
比如上一篇文章中的TodoItem,可以改造為:
export const TodoItem = types.model('TodoItem', {id: types.identifier,title: types.string,done: types.boolean,}); 復(fù)制代碼使用引用類(lèi)型進(jìn)行跟蹤,types.reference
改造TodoList:
export const TodoList = types.model('TodoList', {...list: types.array(TodoItem),editTarget: types.reference(TodoItem),...}); 復(fù)制代碼然后在創(chuàng)建Model實(shí)例,或者applySnapshot的時(shí)候,可以將editTarget的值設(shè)定為正在編輯的TodoItem的id值,MST就會(huì)自動(dòng)在list中查找id相同的TodoItem:
const todoList = TodoList.create({list: [{id: '1', title: 'Todo 1', done: true},{id: '2', title: 'Todo 2', done: true},...],editTarget: '1' });//此時(shí)的editTarget就是list中id為'1'的TodoItem對(duì)象 console.log(todoList.list[0] === todoList.editTarget); // truetodoList.editTarget = todoItem2; // todoItem2為id為'2'的TodoItem對(duì)象 console.log(getSnapshot(todoList).editTarget === '2'); // truetodoList.editTarget = '2' as any; console.log(getSnapshot(todoList).editTarget === '2'); // true 復(fù)制代碼上面的代碼說(shuō)明,reference類(lèi)型的字段本質(zhì)上維護(hù)的是目標(biāo)的標(biāo)識(shí)字段值,并且,除了將目標(biāo)對(duì)象賦值給reference字段外,將目標(biāo)標(biāo)識(shí)字段值賦值給reference字段的效果是一樣的。
另外,reference不僅僅能搭配array使用,也能在map中查找:
const TodoList = types.model('TodoList', {todoMap: types.map(TodoItem),editTarget: types.reference(TodoItem) }); 復(fù)制代碼甚至,MST也允許你自定義查找器(resolver),給types.reference指定第二個(gè)參數(shù),比如官網(wǎng)的這個(gè)例子:
const User = types.model({id: types.identifier,name: types.string })const UserByNameReference = types.maybeNull(types.reference(User, {// given an identifier, find the userget(identifier /* string */, parent: any /*Store*/) {return parent.users.find(u => u.name === identifier) || null},// given a user, produce the identifier that should be storedset(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" }) 復(fù)制代碼types.identifierNumber
若對(duì)象的唯一標(biāo)識(shí)字段的值為數(shù)值類(lèi)型,那么可以使用types.identifierNumber代替types.identifier。
types.safeReference
這是一個(gè)“安全”的引用類(lèi)型:
const Todo = types.model({ id: types.identifier }) const Store = types.model({todos: types.array(Todo),selectedTodo: types.safeReference(Todo) }); 復(fù)制代碼當(dāng)selectedTodo引用的目標(biāo)從todos這個(gè)節(jié)點(diǎn)被移除后,selectedTodo會(huì)自動(dòng)被設(shè)置為undefined。
小結(jié)
MST提供的類(lèi)型和類(lèi)型方法非常齊全,利用好他們就能為任意數(shù)據(jù)定義恰當(dāng)?shù)念?lèi)型。
喜歡本文的歡迎關(guān)注+收藏,轉(zhuǎn)載請(qǐng)注明出處,謝謝支持。
轉(zhuǎn)載于:https://juejin.im/post/5c526c6451882542ff1297ed
總結(jié)
以上是生活随笔為你收集整理的[MobX State Tree数据组件化开发][3]:选择正确的types.xxx的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 使用Java实现K-Means聚类算法
- 下一篇: 那些影响深远的弯路