Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >在 TS 中如何减少重复代码

在 TS 中如何减少重复代码

作者头像
阿宝哥
发布于 2020-05-06 07:21:47
发布于 2020-05-06 07:21:47
2.4K0
举报
文章被收录于专栏:全栈修仙之路全栈修仙之路

相信有些读者已经听说过 DRY 原则,DRY 的全称是 —— Don’t Repeat Yourself ,是指编程过程中不写重复代码,将能够公共的部分抽象出来,封装成工具类或者用抽象类来抽象公共的东西,从而降低代码的耦合性,这样不仅提高代码的灵活性、健壮性以及可读性,也方便后期的维护。

接下来,本文将介绍在 TypeScript 项目开发过程中,如何参考 DRY 原则尽量减少重复代码。减少重复的最简单方法是命名类型,而不是通过以下这种方式来定义一个 distance 函数:

代码语言:txt
AI代码解释
复制
function distance(a: {x: number, y: number}, b: {x: number, y: number}) { 
  return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}

在上述的 distance 方法中,我们重复使用 {x: number, y: number} 来定义参数 a 和参数 b 的类型,要解决这个问题很简单,我们可以定义一个 Point2D 接口:

代码语言:txt
AI代码解释
复制
interface Point2D { 
  x: number;
  y: number;
}

function distance(a: Point2D, b: Point2D) { /* ... */ }

然而在实际的开发过程中,重复的类型并不总是那么容易被发现。有时它们会被语法所掩盖。如果多个函数共享相同的类型签名,比如:

代码语言:txt
AI代码解释
复制
function get(url: string, opts: Options): Promise<Response> { /* ... */ } 
function post(url: string, opts: Options): Promise<Response> { /* ... */ }

对于上面的 get 和 post 方法,为了避免重复的代码,我们可以提取统一的类型签名:

代码语言:txt
AI代码解释
复制
type HTTPFunction = (url: string, opts: Options) => Promise<Response>; 

const get: HTTPFunction = (url, opts) => { /* ... */ };
const post: HTTPFunction = (url, opts) => { /* ... */ };

对于 TypeScript 初学者来说,在定义接口的时候也要小心,避免出现以下类似的重复代码。比如:

代码语言:txt
AI代码解释
复制
interface Person {
  firstName: string;
  lastName: string;
}

interface PersonWithBirthDate {
  firstName: string;
  lastName: string;
  birth: Date;
}

很明显,相对于 Person 接口来说,PersonWithBirthDate 接口只是多了一个 birth 属性,其他的属性跟 Person 接口是一样的。那么如何避免出现例子中的重复代码呢?要解决这个问题,可以利用 extends 关键字:

代码语言:txt
AI代码解释
复制
interface Person { 
  firstName: string; 
  lastName: string;
}

interface PersonWithBirthDate extends Person { 
  birth: Date;
}

当然除了使用 extends 关键字之外,也可以使用交叉运算符(&):

代码语言:txt
AI代码解释
复制
type PersonWithBirthDate = Person & { birth: Date };

下面我们来继续看另一个例子,假设你已经定义 State(代表整个应用程序的状态)和 TopNavState(只代表部分应用程序的状态)两个接口:

代码语言:txt
AI代码解释
复制
interface State {
  userId: string;
  pageTitle: string;
  recentFiles: string[];
  pageContents: string;
}

interface TopNavState {
  userId: string;
  pageTitle: string;
  recentFiles: string[];
}

上述的 TopNavState 接口相比 State 接口只是缺少了 pageContents 属性,但我们却重复声明其他三个相同的属性。为了减少重复代码,我们可以这样做:

代码语言:txt
AI代码解释
复制
type TopNavState = {
  userId: State['userId']; 
  pageTitle: State['pageTitle']; 
  recentFiles: State['recentFiles'];
};

在上面代码中,我们通过成员访问的语法来提取对象中属性的类型,从而避免重复定义接口中相关属性的类型。但这并没有解决本质的问题,我们还有很大的优化空间。针对这个问题,我们可以利用映射类型来进一步做优化:

代码语言:txt
AI代码解释
复制
type TopNavState = {
  [k in 'userId' | 'pageTitle' | 'recentFiles']: State[k]
};

鼠标悬停在 TopNavState 显示它的声明,实际上,这个定义与前一个定义完全相同。

通过映射类型优化后的代码,相比 TopNavState 接口最初的代码简洁了许多。那还有没有优化空间呢?其实是有的,我们可以利用 TypeScript 团队为我们开发者提供的工具类型,这里我们可以使用 Pick

代码语言:txt
AI代码解释
复制
type TopNavState = Pick<
  State, 'userId' | 'pageTitle' | 'recentFiles'
>;

其实除了 Pick 之外,在实际开发过程我们还可以利用其他内置的工具类型来减少重复代码。这里我们再来介绍另一个比较常用的工具类型,即 Partial。以下是未使用 Partial 的例子:

代码语言:txt
AI代码解释
复制
interface Options {
  width: number;
  height: number;
  color: string;
  label: string;
}

interface OptionsUpdate {
  width?: number;
  height?: number;
  color?: string;
  label?: string;
}

class UIWidget {
  constructor(init: Options) {
    /* ... */
  }
  update(options: OptionsUpdate) {
    /* ... */
  }
}

在以上示例中,我们定义了 Options 和 OptionsUpdate 两个接口,它们分别用于描述 UIWidget 的初始化配置项和更新配置项。相比初始化配置项,更新配置项的所有属性都是可选的。

现在我们来开始优化上述的代码,我们先来看一下不使用 Partial 的情形:

代码语言:txt
AI代码解释
复制
type OptionsUpdate = {[k in keyof Options]?: Options[k]};

keyof 操作符接受一个类型,并返回一个由 key 组成的联合类型:

代码语言:txt
AI代码解释
复制
type OptionsKeys = keyof Options;
// Type is "width" | "height" | "color" | "label"

in 操作符是用来遍历枚举类型或联合类型。接着,我们来看一下使用 Partial 的情形:

代码语言:txt
AI代码解释
复制
class UIWidget {
  constructor(init: Options) { /* ... */ } 
  update(options: Partial<Options>) { /* ... */ }
}

其实 Partial 并没有什么神奇的地方,我们来看一下它的定义:

代码语言:txt
AI代码解释
复制
// node_modules/typescript/lib/lib.es5.d.ts

/**
 * Make all properties in T optional
 */
type Partial<T> = {
    [P in keyof T]?: T[P];
};

在以上代码中,首先通过 keyof T 拿到 T 的所有属性名,然后使用 in 进行遍历,将值赋给 P,最后通过 T[P] 取得相应的属性类型。中间的 ? 号,用于将所有属性变为可选。

有时候,你可能还会发现自己想要定义一个类型来匹配一个初始配置项的形状,比如:

代码语言:txt
AI代码解释
复制
const INIT_OPTIONS = {
  width: 640,
  height: 480,
  color: "#00FF00",
  label: "VGA",
};

interface Options {
  width: number;
  height: number;
  color: string;
  label: string;
}

对于 Options 接口来说,我们还可以使用 typeof 操作符来快速定义该接口类型:

代码语言:txt
AI代码解释
复制
type Options = typeof INIT_OPTIONS;

此外,在使用可辨识联合(代数数据类型或标签联合类型)的过程中,也可能出现重复代码。比如:

代码语言:txt
AI代码解释
复制
interface SaveAction { 
  type: 'save';
  // ...
}

interface LoadAction {
  type: 'load';
  // ...
}

type Action = SaveAction | LoadAction;
type ActionType = 'save' | 'load'; // Repeated types!

为了避免重复定义 'save''load',我们可以使用前面提到的成员访问语法,来提取对象中属性的类型:

代码语言:txt
AI代码解释
复制
type ActionType = Action['type']; // 类型是 "save" | "load"

这里需要注意的是,Action['type'] 返回的是联合类型,而如果我们使用前面介绍的 Pick 工具类型,它会返回一个含有 type 属性的接口:

代码语言:txt
AI代码解释
复制
type ActionRec = Pick<Action, 'type'>; // {type: "save" | "load"}

本文通过一些简单的示例,介绍了在 TypeScript 开发过程中如何减少重复代码,其实除了文中介绍了 PickPartial 之外,TypeScript 团队还为我们开发者提供了很多工具类型,可用于减少重复代码和提高开发效率,感兴趣的读者可以阅读本人之前写的 掌握 TS 这些工具类型,让你开发事半功倍 这篇文章。


本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/05/05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
编写高效 TS 代码的一些建议
TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。本文阿宝哥将分享编写高效 TS 代码的 5 个建议,希望这些建议对大家编写 TS 代码能有一些帮助。
阿宝哥
2020/09/22
3.2K0
TS 从 0 到 1 - 泛型进阶
设计泛型是为了在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值。
Cellinlab
2023/05/17
7610
TS 从 0 到 1 - 泛型
在 C# 和 Java 中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。这样用户就可以以自己的数据类型来使用组件。
Cellinlab
2023/05/17
4270
编写TypeScript工具类型,你需要知道的知识
用 JavaScript 编写中大型程序是离不开 lodash 工具的,而用 TypeScript 编程同样离不开工具类型的帮助,工具类型就是类型版的 lodash 。简单的来说,就是把已有的类型经过类型转换构造一个新的类型。工具类型本身也是类型,得益于泛型的帮助,使其能够对类型进行抽象的处理。工具类型主要目的是简化类型编程的过程,提高生产力。
WahFung
2020/08/24
1.4K0
深入浅出 TypeScript
本文是阅读小册 「《深入浅出 TypeScript》」 的阅读笔记,对TypeScript感兴趣的同学请继续阅读吧。
chuckQu
2022/08/19
2.9K0
深入理解 TypeScript 中的 Keyof 运算符,让你的代码更安全、更灵活!
keyof 运算符是在 TypeScript 2.1 版本中引入的。这个关键字已经成为 TypeScript 中高级类型的基石,并在代码中经常使用。它被称为索引查询运算符,因为该关键字会查询 keyof 后指定的类型。索引基类型查询从属性及其相关元素(如默认关键字及其数据类型)中获取值和属性。
前端达人
2024/06/14
3550
深入理解 TypeScript 中的 Keyof 运算符,让你的代码更安全、更灵活!
《现代Typescript高级教程》泛型和类型体操
泛型和类型体操(Type Gymnastics)是 TypeScript 中高级类型系统的重要组成部分。它们提供了强大的工具和技巧,用于处理复杂的类型操作和转换。
linwu
2023/07/27
4660
TypeScript在项目开发中的应用实践体会
从2020年年底的时候,我开始使用Typescript进行项目的开发。期间团队也开始转向Typescript。
@超人
2021/07/29
2.9K0
TypeScript在项目开发中的应用实践体会
【TS】1294- 搞懂 TypeScript 中的映射类型(Mapped Types)
本文会和大家详细介绍 TypeScript 中的映射类型(Mapped Type),看完本文你将学到以下知识点:
pingan8787
2022/06/07
2.4K0
【TS】1294- 搞懂 TypeScript 中的映射类型(Mapped Types)
TypeScript进阶 之 重难点梳理
JavaScript 毋庸置疑是一门非常好的语言,但是其也有很多的弊端,其中不乏是作者设计之处留下的一些 “bug”。当然,瑕不掩瑜~
Nealyang
2020/03/25
3.9K0
TypeScript进阶 之 重难点梳理
TypeScript 强大的类型别名
类型别名会给一个类型起个新名字。类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。
coder_koala
2019/11/20
3.4K0
细数 TS 中那些奇怪的符号
TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。
阿宝哥
2020/09/10
6K1
细数 TS 中那些奇怪的符号
你不知道的 TypeScript 泛型(万字长文,建议收藏)
泛型是 TypeScript(以下简称 TS) 比较高级的功能之一,理解起来也比较困难。泛型应用场景非常广泛,很多地方都能看到它的影子。平时我们阅读开源 TS 项目源码,或者在自己的 TS 项目中使用一些第三方库(比如 React)的时候,经常会看到各种泛型定义。如果你不是特别了解泛型,那么你很可能不仅不会用,不会实现,甚至看不懂这是在干什么。
学前端
2020/07/24
4.2K0
你不知道的 TypeScript 泛型(万字长文,建议收藏)
TypeScript 高级类型总结(含代码案例)
TypeScript 是一种类型化的语言,允许你指定变量、函数参数、返回的值和对象属性的类型。
疯狂的技术宅
2020/12/31
1.3K0
TypeScript的另一面:类型编程
作为前端开发的趋势之一,TypeScript 正在越来越普及,很多人像我一样写了 TS 后再也回不去了,比如写再小的demo也要用 TS(得益于ts-node[1]),JS 只有在配置文件如Webpack(实际上,接下来肯定会有用TS写配置文件的趋势,如Vite)、ESLint等时才会用到。但同样,也有部分开发者对TS持有拒绝的态度,如nodemon的作者就曾表示自己从来没有使用过TS(见 #1565[2])。但同样还有另外一部分人认为TS学习成本太高,所以一直没有开始学习的决心。
zz_jesse
2021/07/30
1.7K0
速查手册 - TypeScript 高级类型 cheat sheet
温馨提示:因微信中外链都无法点击,请通过文末的” “阅读原文” 到技术博客中完整查阅版;(本文整理自技术博客)
JSCON简时空
2020/03/31
1.3K0
让你的TypeScript代码更优雅,这10个特性你需要了解下
在这个技术飞速发展的时代,掌握TypeScript的这些高级功能,不仅可以让你的代码更加健壮,还能大大提升你的开发效率。赶紧来看看吧!
前端达人
2024/06/14
3400
让你的TypeScript代码更优雅,这10个特性你需要了解下
TS 从 0 到 1 - TypeScript 中的各种符号
! 后缀表达式可以用于断言操作对象是非 null 和非 undefined 类型。即 x!,将从 x 值域中排除 null 和 undefined。
Cellinlab
2023/05/17
1.6K0
什么是 TypeScript 4.1 中的模板字面类型?
写了这么多年 TypeScript,最大的感触就是它非常易于理解——特别是对于具有 Java 背景的人。 但是,在听说了 TypeScript 4.1(该语言最近的重大更新)的新闻之后,我还是为新鲜的特性感到惊奇。
一只图雀
2021/01/05
4K0
什么是 TypeScript 4.1 中的模板字面类型?
TypeScript基础知识
TypeScript是JavaScript的一个超集,支持ECMAScript6标准。
岳泽以
2022/11/22
2.3K0
TypeScript基础知识
相关推荐
编写高效 TS 代码的一些建议
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档