TypeScript 数据模型层编程的最佳实践

news2024/11/28 12:52:48

虽然 TypeScript 主要用于客户端,而数据模型的设计主要是服务端来做的。 但是要写出优雅的代码,也还是有不少讲究的。

让我们从一个简单的我的文章列表 api 返回的数据开始,返回的文章列表的信息如下:

 {"id": 2018,"title" : "TypeScript 数据模型层的编程最佳实践","created" : 1530321232,"last_modified" : 1530320620,"status": 1
} 

同时服务端告诉我们说:

status 各值的意思 0/未发布, 1/已发布, 2/已撤回

最佳实践一: 善用枚举,No Magic constant

对于 status 这种可枚举的值,为了避免写出 status === 1 这种跟一个魔法常量的比较的代码,最佳的做法是写一个枚举,并配套一个格式化为字符串表示的函数,如下:

/**
 * 文章状态
 */
const enum PostStatus {/** * 草稿 */draft = 0,/** * 已发布 */published = 1,/** * 已撤回 */revoked = 2
}

function formatPostStatus(status: PostStatus) {switch (status) {case PostStatus.draft:return "草稿";case PostStatus.published:return "已发布";case PostStatus.revoked:return "已撤回";}
} 

如果 PostStatus 状态比较多的话,根据喜好可以写成下面的这样。

function formatPostStatus(status: PostStatus) {const statusTextMap = {[PostStatus.draft]: "草稿",[PostStatus.published]: "已发布",[PostStatus.revoked]: "已撤回"};return statusTextMap[status];
} 

考虑到返回的 created 是时间戳值,我们还需要添加一个格式化时间戳的函数:

 const enum TimestampFormatterStyle {date,time,datetime
}

function formatTimestamp(timestamp: number,style: TimestampFormatterStyle = TimestampFormatterStyle.date
): string {const millis = timestamp * 1000;const date = new Date(millis);switch (style) {case TimestampFormatterStyle.date:return date.toLocaleDateString();case TimestampFormatterStyle.time:return date.toLocaleTimeString();case TimestampFormatterStyle.datetime:return date.toLocaleString();}
} 

最佳实践二:如非必要,不要使用类

上来就搞个数据类

一开始的时候,由于之前的编程经验的影响,我一上来就搞一个数据类。如下:

class Post {id: number;title: string;created: number;last_modified: number;status: number;constructor(id: number,title: string,created: number,last_modified: number,status: number) {this.id = id;this.title = title;this.created = created;this.last_modified = last_modified;this.status = status;}
} 

这可谓分分钟就写了 20 行代码。 然后如果你想到了 TS 提供了简写的方式的话,可以将上面的代码简写如下。

class Post {constructor( readonly id: number,readonly title: string,readonly created: number,readonly last_modified: number,readonly status: number ) {}
} 

也就是说在构造函数中的参数前面添加如 readonly,public,private 等可见性修饰符的话,即可自动创建对应字段。 因为我们是数据模型,所以我们选择使用 readonly

一般再在 Post 添加几个 Getter ,用于返回格式化好的要显示的属性值。 如下:

class Post{
 // 构造函数同上
 
 get createdDateString(): string {return formatTimestamp(this.created, TimestampFormatterStyle.date);}get lastModifiedDateString(): string {return formatTimestamp(this.last_modified, TimestampFormatterStyle.date);}get statusText(): string {return formatPostStatus(this.status);}
} 

麻烦的开始

好了现在数据类写好,准备请求数据,绑定数据了。 一开始我们写出如下代码:

const posts:Post[] = resp.data 

然后 TS 报如下错误:

[ts]
Type '{ id: number; title: string; created: number; last_modifistatic fromJson(json: JsonObject): Post {return new Post(json.id,json.title,json.created,json.last_modified,json.status);}ed: number; status: number; }[]' is not assignable to type 'Post[]'.Type '{ id: number; title: string; created: number; last_modified: number; status: number; }' is not assignable to type 'Post'.Property 'createdDateString' is missing in type '{ id: number; title: string; created: number; last_modified: number; status: number; }'. 

此时我们开始意识到,请求回来的jsondata 列表是普通的 object 不能直接给 Post 赋值。 由于一些编程惯性,我们开始想着,是不是反序列化一下,将json 对象反序列化成 Post. 于是我们在 Post 类中添加如下的反序列化方法。

type JsonObject = { [key: string]: any };
class Post{ // 其他代码同上  static fromJson(json: JsonObject): Post {return new Post(json.id,json.title,json.created,json.last_modified,json.status);}
} 

然后在请求结果处理上增加一过 map 用于反序列化的转换。如下:

const posts: Post[] = resp.data.map(Post.fromJson); 

代码写到这里,思考一下,原来 json 就是一个原生的 JavaScript 对象了。但是我们又再一步又用来构造出 Post 类。这一步显得多余。 另外虽然一般我们的模型代码比如 Post 其实可以根据 api 文档自动生成, 但是也还是增加不少代码。

开始改进

怎么改进呢? 既然我们的 json 已经是 JavaScrit 对象了,我们只是缺少类型声明。 那我们直接加上类型声明的,而且 TS 中的类型声明,编译成 js 代码之后会自动清除的,这样可以减少代码量。这对于小程序开发来说还是很有意义的。

自然我们写出如下代码。

interface Post {id: number;title: string;created: number;last_modified: number;status: number;
} 

此时,为了 UI 模板数据上的绑定。 我们双增加了一个叫 PostInfo 的接口。然后将代码修改如下:

interface PostInfo {statusText: string;createdDateString: string;post: Post;
}

function getPostInfoFromPost(post: Post): PostInfo {const statusText = formatPostStatus(post.status);const createdDateString = formatTimestamp(post.created);return { statusText, createdDateString, post };
}

const postInfos: PostInfo[] = (resp.data as Post[]).map(getPostInfoFromPost); 

其实你已知知道猫的样子

其实我想说的是,我们上面的代码中 Post 接口是多余的。 直接看代码:

const postDemo = {id: 2018,title: "TypeScript 数据模型层的编程最佳实践",created: 1530321232,last_modified: 1530320620,status: 1
};

type Post = typeof postDemo; 

当把鼠标放到 Post 上时,可以看到如下类型提示:

所以在开发开始时,可以先直接用 API 返回的数据结构当作一个数据模型实例。然后使用 typeof 来得到对应的类型。

把套去掉

PostInfo 这样包装其实挺丑陋的, 因为在我们心里这里其实应该是一个 Post 列表,但是为了格式化一些数据显示,我们弄一个 PostInfo 的包装,这样在使用上带来很多不方便。因为当你要使用 Post 的其他的值时,你总需要多一次间接访问比如这样 postInfo.post.id。 这就PostInfo 是我们在使用 Post 实例时的一个枷锁,一个套, 现在我们来将这个套去掉。而去掉这个套的方法使用了两项技术。 一个是 TS 中接口的继承,一个是 Object.assign 这个方法。 直接用代码说话:

interface PostEx extends Post {statusText: string;createdDateString: string;
}

function getPostExFromPost(post: Post): PostEx {const statusText = formatPostStatus(post.status);const createdDateString = formatTimestamp(post.created);return Object.assign(post, { statusText, createdDateString });
}

const posts: PostEx[] = (resp.data as Post[]).map(getPostExFromPost); 

即保证了类型安全,使用上又方便,代码也不失优雅。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/166542.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

原生PHP及thinkphp6接入阿里云短信

申请accesskey获取到Accesskey ID和Accesskey Secret保存下来,一会要用到添加测试手机号,在接口测试能否正常发送下载阿里云短信sdk,使用composer下载,没有安装请先安装安装可以安装到任意文件夹下,后面代码写好后&…

TDSQL的安装教程(低配体验)

一、了解TDSQL tdsql腾讯云文档 TDSQL-C MySQL 版(TDSQL-C for MySQL)是腾讯云自研的新一代云原生关系型数据库。融合了传统数据库、云计算与新硬件技术的优势,为用户提供具备极致弹性、高性能、海量存储、安全可靠的数据库服务。TDSQL-C My…

AtCoder Beginner Contest 285解题报告

A - Edge Checker 2 Problem Statement Determine if there is a segment that directly connects the points numbered a and b in the figure below. Constraints 1≤a<b≤15a and b are integers.Input The input is given from Standard Input in the following for…

用SpectorJS调试WebGL应用

随着使用 WebGL 构建的体验不断涌现&#xff0c;以及 WebVR/AR 领域的所有改进&#xff0c;拥有高效的调试工具变得至关重要。 无论你是刚刚起步还是已经是使用 WebGL 开发 3D 应用程序的经验丰富的开发人员&#xff0c;都可能知道工具对于生产力的重要性。 在寻找此类工具时&…

【开发环境】JRE 裁剪 ① ( 裁剪 bin 目录下的 dll 动态库文件 )

文章目录一、JRE 裁剪二、裁剪 bin 目录下的 dll 动态库文件参考博客 : 精简jre1.8精简jre步骤裁剪JRE(嵌入式设备的java环境移植) 资源下载地址 : https://download.csdn.net/download/han1202012/87388400 一、JRE 裁剪 在 【IntelliJ IDEA】使用 exe4j 生成 jre jar 可执…

华为MPLS跨域A、B方案实验配置

目录 MPLS域内配置 MPLS-AS100域内配置 MPLS-AS200域内配置 域间方式A配置 ASBR4和ASBR5配置实例 ASBR之间建立基于实例的EBGP邻居关系 域间方式B配置 ASBR相连接口开启MPLS ASBR之间建立MP-BGP的EBGP邻居 配置取消RT值检测 配置传递路由时更改下一跳为自身 MPLS域内…

程序员必知必会 QPS TPS、URI URL、PV UV GMV

一、QPS和 TPS QPS&#xff1a;Queries Per Second&#xff0c;意思是“每秒查询数”&#xff0c;是一台服务器每秒能够响应的查询次数&#xff0c;是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。 即最大吞吐能力。 TPS&#xff1a;TransactionsPerSecond&…

springboot整合log4j2

导入依赖 <dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version> </dependency><!--log4j2--> <dependency><groupId>org.apache.logging.log4j</groupId>&…

Spring Boot(五十三):SpringBoot Actuator之实现

1 场景介绍 对于一个大型的几十个、几百个微服务构成的微服务架构系统&#xff0c;在线上时通常会遇到下面一些问题&#xff0c;比如&#xff1a; 1. 如何知道哪些服务除了问题&#xff0c;如何快速定位&#xff1f; (健康状况&#xff09; 2. 如何统一监控各个微服务的性能指标…

JAVA会员营销系统源码+数据库,实体店铺会员管理和营销系统源码,采用SpringBoot + Mysql+Mybatis

会员营销系统介绍 介绍 fuint会员营销系统是一套开源的实体店铺会员管理和营销系统。系统基于前后端分离的架构&#xff0c;后端采用Java SpringBoot Mysql&#xff0c;前端基于当前流行的Uniapp&#xff0c;Element UI&#xff0c;支持小程序、h5。主要功能包含电子优惠券、…

冰蝎V4.0流量分析到攻防检测

0x01 前言 最近在改写 yso&#xff0c;觉得自己基础太差了&#xff0c;想先阅读一下 sqlmap、冰蝎以及一些其他工具的开发思路。文章可能写的不够严谨&#xff0c;有不对的地方还请师傅们多多指出。 0x02 环境搭建 这里我看的是 MountCloud 师傅所二开的冰蝎项目&#xff0c…

【关于Linux中----进程间通信方式之system V共享内存】

文章目录一、共享内存示意图二、学习共享内存前的准备工作三、共享内存函数3.1创建共享内存&#xff1a;3.2控制共享内存&#xff1a;3.3挂接和去挂接&#xff1a;一、共享内存示意图 上一篇文章中讲述的是管道的通信方式&#xff0c;而这里要讲的是操作系统层面专门为进程间通…

编译原理-链接实例分析

gcc-arm-none-eabi 工具链功能1.arm-none-eabi-gcc &#xff1a;c语言编译器&#xff0c;可以将.c文件编译为.o的执行文件2.arm-none-eabi-g &#xff1a;c编译器&#xff0c;可以将.cpp文件编译成.o的执行文件3.arm-none-eabi-ld : 链接器&#xff0c;链接所有的.o文件生成可执…

CDH6.3生产环境中禁用Kerberos

在集群启用Kerberos后&#xff0c;会对现有环境的部分代码做改造&#xff0c;有些人觉得使用起来不方便&#xff0c;想取消Kerberos。本篇文章主要介绍如何禁用CDH集群的Kerberos及禁用后对各组件服务的测试。修改了网上相关文档的一些缺陷&#xff0c;在生产环境中实际使用过通…

GIT ---- GitHub配置SSH Key的完整步骤

1. 配置 SSH Key 由于提交代码每次输入用户名和密码&#xff0c;很繁琐&#xff0c;所以直接配置 SSH Key&#xff0c;直接自动验证&#xff0c;减少提交代码的操作步骤。 2. 查看配置命令 git config --list 查看当前Git环境所有配置&#xff0c;还可以配置一些命令别名之类…

这一年,熬过许多夜,也有些许收获 | 2022年度总结

弹指一挥间&#xff0c;时间如白驹过隙。光阴似箭&#xff0c;日月如梭&#xff0c;时间如闪电&#xff0c;转瞬即逝。回望来时路&#xff0c;不觉潸然泪下… 一说到年终总结&#xff0c;好像都离不开这样煽情的开场白。但不可否认的是&#xff0c;时间确实过得很快&#xff0…

学习记录661@项目管理之项目立项管理

什么是项目立项管理 项目立项管理关注的重点在于是否要启动一个项目&#xff0c;并为其提供相应的预算支持具体来说&#xff0c;项目立项管理包括以下 5 个典型环节&#xff0c;分别是 项目建议项目可行性分析项目审批项目招投标项目合同谈判与签订 需要说明的是&#xff0c…

两大技巧教你轻松应对IB数学

同学想要在IB数学科取得好成绩&#xff0c;可以从两个方面来着手。 1.复习技巧第一个是复习技巧。这方面&#xff0c;同学要清楚知道自己读的课程&#xff0c;它的教学大纲&#xff08;Syllabus&#xff09;要求是什么&#xff0c;还有它背后想要同学达到什么样的目标。 IB数学…

浅谈DNS解析

DNS介绍IP是计算机里的地址簿&#xff0c;但是IP是由一串数字组成&#xff0c;我们的大脑很难记住&#xff0c;所以就需要定义一个符合人类记忆规则的地址&#xff0c;而这就是我们现在常用的网站域名&#xff0c;域名就是我们和计算机作为地址沟通的桥梁&#xff0c; 虽然我们…

物流企业如何确保网络安全?

随着网上购物的发展&#xff0c;人们的日常生活越来越离不开物流企业的服务了。而且在一些企业的供应链中&#xff0c;物流运输也是非常重要的一环节。与此同时&#xff0c;伴随着供应链数字化&#xff0c;透明度、速度和成本优势增加了公司对技术的兴趣。物流企业也更喜欢使用…