定义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提供的类型和类型方法很是齐全,利用好他们就能为任意数据定义恰当的类型。
喜欢本文的欢迎关注+收藏,转载请注明出处,谢谢支持。