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

简介:

虽然 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 上时,可以看到如下类型提示:

Easy Post interface from

所以在开发开始时,可以先直接用 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);

复制代码

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


作者:banxi
原文发布时间:2018年06月30日
本文来源 掘金如需转载请紧急联系作者
相关文章
|
22天前
|
JavaScript 前端开发 开发者
深入理解TypeScript:类型系统与最佳实践
【10月更文挑战第8天】深入理解TypeScript:类型系统与最佳实践
|
3月前
|
JavaScript 前端开发 安全
揭秘TypeScript的魔力:它是如何华丽变身为JavaScript的超能英雄,让您的代码飞入全新的编程维度!
【8月更文挑战第22天】在Web开发领域,JavaScript是最主流的编程语言之一。但随着应用规模的增长,其类型安全和模块化的不足逐渐显现。为解决这些问题,微软推出了TypeScript,这是JavaScript的一个超集,通过添加静态类型检查来提升开发效率。TypeScript兼容所有JavaScript代码,并引入类型注解功能。
37 2
|
3月前
|
JavaScript 前端开发 编译器
Angular 与 TypeScript 强强联手太厉害啦!强类型编程带来巨大开发优势,快来一探究竟!
【8月更文挑战第31天】作为一名前端开发者,我致力于探索各种技术框架以提升开发效率与代码质量。近期深入研究了Angular与TypeScript的结合,体验到强类型编程带来的显著优势。Angular是一款强大的前端框架,而TypeScript则是由微软开发的一种强类型语言,为JavaScript增添了静态类型检查等功能。
33 0
|
3月前
|
JavaScript 测试技术 API
Vue 3 与 TypeScript:最佳实践详解
Vue 3 与 TypeScript:最佳实践详解
|
6月前
|
JavaScript 前端开发 编译器
【JavaScript与TypeScript技术专栏】从JavaScript迁移到TypeScript的最佳实践
【4月更文挑战第30天】本文介绍了从 JavaScript 迁移到 TypeScript 的最佳实践。首先,学习 TypeScript 基础并安装编译器。接着,采取逐步迁移策略,保持与 JavaScript 兼容,利用类型定义和接口增强代码可读性。此外,借助 TSLint 和 Prettier 保证代码质量与风格一致性,使用类型定义文件提升第三方库的可维护性。
63 0
|
6月前
|
JavaScript IDE 编译器
TypeScript中模块路径解析与配置:深入剖析与最佳实践
【4月更文挑战第23天】本文深入探讨了TypeScript中模块路径解析的原理与配置优化,包括相对路径、Node.js模块解析和路径别名。通过配置`baseUrl`、`paths`、`rootDirs`以及避免裸模块名,可以提升开发效率和代码质量。建议使用路径别名增强代码可读性,保持路径结构一致性,并利用IDE插件辅助开发。正确配置能有效降低维护成本,构建高效可维护的代码库。
|
6月前
|
JavaScript 前端开发 安全
TypeScript:赋予JavaScript数据类型新的力量,提升编程效率!
TypeScript:扩展JavaScript数据类型,赋予编程更强大的表达能力!
|
6月前
|
JavaScript 前端开发 安全
详细介绍 TypeScript 函数的各种特性、用法和最佳实践
详细介绍 TypeScript 函数的各种特性、用法和最佳实践
83 3
|
JavaScript
TypeScript 支持模块化编程,具体应用案例解析
TypeScript 支持模块化编程,具体应用案例解析
63 0
|
存储 缓存 资源调度
TypeScript Monorepo 最佳实践
TypeScript Monorepo 最佳实践
399 1