博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[MobX State Tree数据组件化开发][3]:选择正确的types.xxx
阅读量:6850 次
发布时间:2019-06-26

本文共 7050 字,大约阅读时间需要 23 分钟。

??

定义Model时,需要正确地定义props中各字段的类型。本文将对MST提供的各种类型以及类型的工厂方法进行简单的介绍,方便同学们在定义props时挑选正确的类型。

前提

定义props之前,有一个前提是,你已经明确地知道这个Model中状态的数据类型。

如果Model用于存放由后端API返回的数据,那么一定要和后端确认返回值在所有情况下的类型。比如,某个字段在没有值的时候你以为会给一个'',而后端却给了个null;或者某个数组你以为会给你一个空数组[],他又甩你一个null如果你不打算自己编写一个标准化数据的方法,那一定要和数据的提供方确定这些细节。

基础类型

types.string

定义一个字符串类型字段。

types.number

定义一个数值类型字段。

types.boolean

定义一个布尔类型字段。

types.integer

定义一个整数类型字段。

注意,即使是TypeScript中也没有“整数”这个类型,在编码时,传入一个带小数的值TypeScript也无法发现其中的类型错误。如无必要,请使用types.number

types.Date

定义一个日期类型字段。

这个类型存储的值是标准的Date对象。在设置值时,可以选择传入数值类型的时间戳或者Date对象。

export const Model = types    .model({        date: types.Date     })    .actions(self => ({        setDate (val: Date | number) {            self.date = date;        }    }));复制代码

types.null

定义一个值为null的类型字段。

types.undefined

定义一个值为undefined的类型字段。

复合类型

types.model

定义一个对象类型的字段。

types.array

定义一个数组类型的字段。

types.array(types.string);复制代码

上面的代码定义了一个字符串数组的类型。

types.map

定义一个map类型的字段。该map的key都为字符串类型,map的值都为指定类型。

types.map(types.number);复制代码

可选类型,types.optional

根据传入的参数,定义一个带有默认值的可选类型。

types.optional是一个方法,方法有两个参数,第一个参数是数据的真实类型,第二个参数是数据的默认值。

types.optional(types.number, 1);复制代码

上面的代码定义了一个默认值为1的数值类型。

注意,types.array或者types.map定义的类型自带默认值(array为[],map为{}),也就是说,下面两种定义的结果是一样的:

// 使用types.optionaltypes.optional(types.array(types.number), []);types.optional(types.map(types.number), {});// 不使用types.optionaltypes.array(types.number);types.map(types.number);复制代码

如果要设置的默认值与types.arraytypes.map自带的默认值相同,那么就不需要使用types.optional

自定义类型,types.custom

如果想控制类型更底层的如序列化和反序列化、类型校验等细节,或者根据一个class或interface来定义类型,可以使用types.custom定义自定义类型。

class Decimal {    ...}const DecimalPrimitive = types.custom
({ 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.union(types.number, types.string);复制代码

联合类型可以有任意个联合的类型。

字面值类型,types.literal

字面值类型可以限制存储的内容与给定的值严格相等。

比如使用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'));复制代码

枚举类型,types.enumeration

枚举类型可以看作是联合类型以及字面值类型的一层封装,比如上面的性别可以使用枚举类型来定义:

const GenderType = types.enumeration('Gender', ['male', 'female']);复制代码

方法的第一个参数是可选的,表示枚举类型的名称。第二个参数传入的是字面值数组。

在TypeScript环境下,可以这样搭配TypeScript枚举使用:

enum Gender {    male,    female}const GenderType = types.enumeration
('Gender', Object.values(Gender));复制代码

可undefined类型,types.maybe

定义一个可能为undefined的字段,并自带默认值undefined

types.maybe(type)// 等同于types.optional(types.union(type, types.literal(undefined)), undefined)复制代码

可空类型,types.maybeNull

types.maybe类似,将undefined替换成了null

types.maybeNull(type)// 等同于types.optional(types.union(type, types.literal(null)), null)复制代码

不可不类型,types.frozen

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, reactivemodel.anyData.b = 3; // not reactive复制代码

也就是只有设置一个新的值给这个字段,相关的observer才会响应状态的更新。而修改这个字段内部的某个值,是不会被捕捉到的。

滞后类型,types.late

有时候会出现这样的需求,需要一个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

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提供了一个优雅的解决方案:引用类型和标识类型。

这两者需要搭配使用才能发挥作用:

定义标识,types.identifier

标识就是数据对象的唯一标识字段,这个字段的值在库中保持唯一,也就是primary_key。

比如上一篇文章中的TodoItem,可以改造为:

export const TodoItem = types  .model('TodoItem', {    id: types.identifier,    title: types.string,    done: types.boolean,  });复制代码

使用引用类型进行跟踪,types.reference

改造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); // truetodoList.editTarget = todoItem2; // todoItem2为id为'2'的TodoItem对象console.log(getSnapshot(todoList).editTarget === '2'); // truetodoList.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.identifierNumber代替types.identifier

types.safeReference

这是一个“安全”的引用类型:

const Todo = types.model({ id: types.identifier })const Store = types.model({    todos: types.array(Todo),    selectedTodo: types.safeReference(Todo)});复制代码

selectedTodo引用的目标从todos这个节点被移除后,selectedTodo会自动被设置为undefined

小结

MST提供的类型和类型方法非常齐全,利用好他们就能为任意数据定义恰当的类型。

喜欢本文的欢迎关注+收藏,转载请注明出处,谢谢支持。

转载于:https://juejin.im/post/5c526c6451882542ff1297ed

你可能感兴趣的文章
autofs按需挂载配置
查看>>
zabbix自动监控Linux端口
查看>>
使用 TRegistry 类[3]: 创建与删除
查看>>
heartbeat v1 + ldirctord
查看>>
类的静态成员问题
查看>>
Git学习(二)
查看>>
Linux下同步工具inotify+rsync使用详解
查看>>
Google Authenticator安全配置ssh二次验证登录
查看>>
决心书
查看>>
我的linux学习,第一天!
查看>>
基于python的一个运维自动化的项目(进度更新)【已开源】
查看>>
SiteMesh 2.X 的使用(网页结构模板)
查看>>
MYSQL-mysqlslap
查看>>
大数据时代,野心勃勃的MongDB为你处理海量数据!
查看>>
jxl 读取/写入excel
查看>>
Mongodb主从复制
查看>>
Cisco ASA5500解决内网用公网IP不能访问DMZ区服务器的
查看>>
Windows7常用命令
查看>>
LaTex 制作简历
查看>>
windows8系统装机总结
查看>>