How to reduce duplicate code in TS

Posted May 27, 20205 min read

I believe that some readers have heard of the DRY principle. The full name of DRY is-Don't Repeat Yourself, which means that no repeated code is written during the programming process, and the common parts are abstracted out and encapsulated into tools or Abstract classes abstract the common things, thereby reducing the coupling of the code, which not only improves the flexibility, robustness and readability of the code, but also facilitates later maintenance.

Next, this article will introduce how to use the DRY principle to minimize duplication of code during the TypeScript project development process. The easiest way to reduce duplication is to name the type instead of defining a distance function in the following way:

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));
}

In the above distance method, we repeatedly use {x:number, y:number} to define the types of parameter a and parameter b. To solve this problem, we can define a Point2D interface:

interface Point2D {
  x:number;
  y:number;
}

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

However, in the actual development process, duplicate types are not always so easy to find. Sometimes they are obscured by grammar. If multiple functions share the same type signature, for example:

function get(url:string, opts:Options):Promise <Response> {/* ... * /}
function post(url:string, opts:Options):Promise <Response> {/* ... * /}

For the above get and post methods, in order to avoid duplicate code, we can extract a unified type signature:

type HTTPFunction =(url:string, opts:Options) => Promise <Response>;

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

For TypeScript beginners, be careful when defining the interface to avoid the following duplicate code. such as:

interface Person {
  firstName:string;
  lastName:string;
}

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

Obviously, compared to the Person interface, the PersonWithBirthDate interface just has a birth attribute, and the other attributes are the same as the Person interface. So how to avoid duplicate code in the example? To solve this problem, you can use the extends keyword:

interface Person {
  firstName:string;
  lastName:string;
}

interface PersonWithBirthDate extends Person {
  birth:Date;
}

Of course, in addition to using the extends keyword, you can also use the cross operator(&):

type PersonWithBirthDate = Person & {birth:Date};

Let's continue to look at another example. Suppose you have defined two interfaces:State(representing the state of the entire application) and TopNavState(representing only the state of some applications):

interface State {
  userId:string;
  pageTitle:string;
  recentFiles:string [];
  pageContents:string;
}

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

Compared with the State interface, the TopNavState interface mentioned above just lacks the pageContents attribute, but we have repeatedly declared three other same attributes. To reduce duplication of code, we can do this:

type TopNavState = {
  userId:State ['userId'];
  pageTitle:State ['pageTitle'];
  recentFiles:State ['recentFiles'];
};

In the above code, we use the syntax of member access to extract the type of the attribute in the object, so as to avoid repeatedly defining the type of the relevant attribute in the interface. But this does not solve the essential problem, we still have a lot of room for optimization. For this problem, we can use the mapping type to further optimize:

type TopNavState = {
  [k in 'userId' | 'pageTitle' | 'recentFiles']:State [k]
};

Hover the mouse over TopNavState to display its statement. In fact, this definition is exactly the same as the previous one.

top-nav-state-definition.jpg

The code optimized by the mapping type is much simpler than the original code of the TopNavState interface. Is there any room for optimization? In fact, we can use the type of tools provided by the TypeScript team to our developers. Here we can use Pick:

type TopNavState = Pick <
  State, 'userId' | 'pageTitle' | 'recentFiles'
>;

In fact, in addition to Pick, in the actual development process, we can also use other built-in tool types to reduce duplication of code. Here we will introduce another commonly used tool type, namely Partial. The following is an example where Partial is not used:

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) {
   /* ... * /
  }
}

In the above example, we defined two interfaces, Options and OptionsUpdate, which are used to describe the initial configuration items and update configuration items of UIWidget, respectively. Compared to initial configuration items, all attributes of the updated configuration items are optional.

Now let's start optimizing the above code, let's first look at the situation without using Partial:

type OptionsUpdate = {[k in keyof Options]?:Options [k]};

The keyof operator accepts a type and returns a union type consisting of key:

type OptionsKeys = keyof Options;
//Type is "width" | "height" | "color" | "label"

The in operator is used to iterate over enumerated types or union types. Next, let's take a look at the use of Partial:

class UIWidget {
  constructor(init:Options) {/* ... * /}
  update(options:Partial <Options>) {/* ... * /}
}

In fact, there is nothing magical about Partial, let's take a look at its definition:

//node_modules/typescript/lib/lib.es5.d.ts

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

In the above code, first get all the attribute names of T through keyof T, then traverse using in, assign the value to P, and finally get the corresponding attribute type through T [P] . The middle ? Sign is used to make all attributes optional.

Sometimes, you may also find that you want to define a type to match the shape of an initial configuration item, such as:

const INIT_OPTIONS = {
  width:640,
  height:480,
  color:"# 00FF00",
  label:"VGA",
};

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

For the Options interface, we can also use the typeof operator to quickly define the interface type:

type Options = typeof INIT_OPTIONS;

In addition, in the process of using recognizable unions(algebraic data types or label joint types), duplicate codes may also appear. such as:

interface SaveAction {
  type:'save';
  //...
}

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

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

To avoid repeating the definition of 'save' and' load', we can use the member access syntax mentioned earlier to extract the type of attributes in the object:

type ActionType = Action ['type']; //The type is "save" | "load"

It should be noted here that Action ['type'] returns a union type, and if we use the Pick tool type introduced earlier, it will return an interface with a type attribute:

type ActionRec = Pick <Action, 'type'>; //{type:"save" | "load"}

This article introduces how to reduce duplication of code during the development of TypeScript through some simple examples. In addition to the introduction of Pick and Partial, the TypeScript team also provides many types of tools for our developers, which can be used to reduce Repeat code and improve development efficiency. Interested readers can read [I master these tool types of TS and let you develop with less effort]( https://mp.weixin.qq.com/s?__biz=MzI2MjcxNTQ0Nw==&mid = 2247484142 & idx = 1 & sn = 946ba90d10e2625513f09e60a462b3a7 & scene = 19 # wechat_redirect) this article.

My full-stack Xianxian Road subscription number, I will regularly share Angular, TypeScript, Node.js related articles, welcome interested friends to subscribe!

learned-ts.jpg