TypeScript provides many powerful type aliases that make it easy to convert one type to another. This article is intended as a quick and basic guide to common type aliases. Before learning about common type aliases, let’s take a look at some TypeScript keywords.

Keywords in TypeScript

extends

T extends U ? X : Y
Copy the code

It is obviously similar to the ternary operator in JS. In fact, you can really think of it as a ternary operator. Type X if T can be assigned to U, Y otherwise. Examples from the official website:

type TypeName<T> =
    T extends string ? "string" :
    T extends number ? "number" :
    T extends boolean ? "boolean" :
    T extends undefined ? "undefined" :
    T extends Function ? "function" :
    "object";
Copy the code

Extends the use of, if T for joint type, will up is similar to the role of the for loop decomposition parameters, if the type of T | A | B and C will be resolved as (A extends U? X : Y) | (B extends U ? X : Y) | (C extends U ? X, Y). Therefore, it is convenient to implement the filtering union type. Examples on the official website are as follows:

type Diff<T, U> = T extends U ? never : T;  // Remove types from T that are assignable to U
type T30 = Diff<"a" | "b" | "c" | "d"."a" | "c" | "f">;  // "b" | "d"

type Filter<T, U> = T extends U ? T : never;  // Remove types from T that are not assignable to U
type T31 = Filter<"a" | "b" | "c" | "d"."a" | "c" | "f">;  // "a" | "c"
Copy the code

keyof

The keyof keyword is used to return all keys of a certain type, resulting in a union type

interface Person {
    name: string,
    age: number
}
type K1 = keyof Person // "name" | "age"
type K2 = keyof any[];  // number | "length" | "toString" | "toLocaleString" | "pop" | "push" | "concat" | "join" | "reverse" | "shift" | "slice" | "sort" | "splice" | "unshift" | "indexOf" | "lastIndexOf" | ... 13 more ... | "values"

type Mapish = { [k: string]: boolean };
type M = keyof Mapish; / / string | number here return string | number because JavaScript object always force the key into the string, so the obj [0] and obj consistently [' 0 ']

class Person1 {
    name = 'xiaoping'
    age: number
    constructor(age: number) {
        this.age = age
    }
    say() {
        console.log(`my name is The ${this.name}`)
    }
}
type P = keyof Person1 // "name" | "age" | "say"
Copy the code

typeof

In JavaScript typeof is an operator that can be used in expressions. Typeof in TypeScript can be used to get the types of variables and properties.

let s = "hello";
let n: typeof s; // string

let person = {
    name: 'xiaoping'.age: 19.address: {
        province: "Shanghai"
    }
}
type P = typeof person
/* type P = {name: string; age: number; address: { province: string; }; } * /

function userInfo(age) {
    return {
        name: 'xiaoping'.age: age
    }
}
type Fn = typeof userInfo
// type Fn = (age: any) => { name: string; age: any; }
Copy the code

infer

Infer is used to represent a type variable to be inferred. This keyword is a little hard to understand, so let’s go straight to the example.

type ReturnType<T> = T extends(... args: any[]) => infer R ? R : any;const add = (x:number, y:number) = > x + y
type t = ReturnType<typeof add> // type t = number
Copy the code

ReturnType represents if T can be assigned to (… Args: any[]) => infer R, then return R; otherwise return any. (… Args: any[]) => infer R represents functions whose parameters are arbitrary values and return values are Inter R. Add obviously does, and the return value of the add function is number. Infer R therefore is of type number. Type t = number inter is not just used to infer return values; its most powerful function is in unpacking. Here’s an example from the official website:

type Unpacked<T> =
    T extends (infer U)[] ? U :
    T extends(... args: any[]) => infer U ? U : Textends Promise<infer U> ? U :
    T;
Copy the code

This looks a little confusing, so let’s break it down:

type Unpacked<T> = T extends (infer U)[] ? U : T
type S1 = string[]
type T1 = Unpacked<S1>// string
Copy the code

Return U if T can be assigned to an array of type infer U [], otherwise return T. So you can easily unpack and get T1 as string. Similarly, the unpacking of Promise can be easily realized.

type Unpacked<T> = T extends Promise<infer U> ? U : T
type T1 = Unpacked<PromiseThe < {name: string, age: number }>> //{ name: string; age: number; }
Copy the code

unknown

In TypeScript, when we are not sure what type a type is, we can declare it any or unkown. However, TypeScript actually recommends using unknown because it is type-safe. If it’s any, you can assign any value without doing any type checking. Unkown, on the other hand, must first assert the type using typeof or Instanceof. Then you can operate.

is

The is keyword is used to determine whether a parameter belongs to a certain type and return the corresponding Boolean type based on the result

function isString(s: unknown) :boolean {
    return typeof s === 'string'
}
function toUpperCase(x: unknown) {
    if (isString(x)) {
        x.toUpperCase() // Error, Object is of type 'unknown'}}Copy the code

In the above example, toUpperCase is called only when s is a string. But errors are still reported because nested functions prevent TypeScript from making proper inferences. This is where the is keyword can be used.

function isString(s: unknown) :s is string {
    return typeof s === 'string'
}

function toUpperCase(x: unknown) {
    if (isString(x)) {
        x.toUpperCase()  // No error is reported at this time}}Copy the code

Powerful built-in type aliases

TypeScript provides powerful type aliases for common type conversions.

Partial<T>

Partial can set all attributes to optional types.

interface Todo {
    title: string.description: string;
}
type PartialTodo = Partial<Todo>
/* equivalent to interface PartialTodo {title? : string; description? : string; } * /
Copy the code

The source code is as follows:

type Partial<T> = {
    [P inkeyof T]? : T[P]; };Copy the code

Required<T>

Required is the opposite of Partial, making all optional types Required.

interfaceProps { a? :number; b? :string;
}
type RequireProps = Required<Props>
/* equivalent to interface RequireProps {a: number; b: string; } * /
Copy the code

The source code is as follows:

type Required<T> = {
    [P inkeyof T]-? : T[P]; };Copy the code

Readonly

Treat all properties as Readonly.

interface Todo {
    title: string.description: string;
}
type ReadonlyTodo = Readonly<Todo>
/* equivalent to interface ReadonlyTodo {readonly title: string, readonly description: string; } * /
Copy the code

The source code is as follows:

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};
Copy the code

Record<K,T>

Construct an object type with properties of type K and property values of type T. Often used to map attributes of one type to another.

interface CatInfo {
    age: number;
    breed: string;
}
type CatName = "miffy" | "boris" | "mordred";
type Cats = Record<CatName, CatInfo> // { miffy: CatInfo; boris: CatInfo; mordred: CatInfo; }
const cats: Cats = {
    miffy: { age: 10.breed: "Persian" },
    boris: { age: 5.breed: "Maine Coon" },
    mordred: { age: 16.breed: "British Shorthair"}},Copy the code

The source code is as follows:

type Record<K extends keyof any, T> = {
    [P in K]: T;
};
Copy the code

Pick<T, K>

Construct a new type by selecting a set of attributes K from T

interface Todo {
    title: string;
    description: string;
    completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">; //
/* equivalent to interface TodoPreview {title: string; completed: boolean; } * /
Copy the code

The source code is as follows:

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};
Copy the code

Exclude<T, E>

Generate a new type by excluding all types in T that can be assigned to E.

type T0 = Exclude<"a" | "b" | "c"."a">;
/ / equivalent type T0 = "b" | "c"

type T1 = Exclude<"a" | "b" | "c"."a" | "b">;
Type T1 = "c"

type T2 = Exclude<string | number | (() = > void), Function>;
/ / equivalent type T2 = string | number
Copy the code

The source code is as follows:

type Exclude<T, U> = T extends U ? never : T;
Copy the code

Omit<T, K>

Subtract some attributes K from T to generate the new type

interface Todo {
  title: string;
  description: string;
  completed: boolean;
  createdAt: number;
}
type TodoPreview = Omit<Todo, "description" | "completed">;
const todo: TodoPreview = {
  title: ' '.createdAt: Date.now()
}
Copy the code

The source code is as follows:

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
Copy the code

Extract<T, U>

Extract the types in T that can be assigned to U and generate a new type. Contrary to Exclude

type T0 = Extract<"a" | "b" | "c"."a" | "f">;
// type T0 = "a"

type T1 = Extract<string | number | (() = > void), Function>;
Type T1 = () => void
Copy the code

The source code is as follows:

type Extract<T, U> = T extends U ? T : never;
Copy the code

NonNullable<T>

Exclude null and undefined from T.

type T0 = NonNullable<string | number | undefined>;
/ / equivalent type T0 = string | number

type T1 = NonNullable<string[] | null | undefined>;
// equivalent to type T1 = string[]
Copy the code

Parameters<T>

Returns an argument of function type T to construct a tuple.

declare function f1(arg: { a: number; b: string }) :void;

type T0 = Parameters<() = > string>;
// type T0 = []

type T1 = Parameters<(s: string) = > void>;
// type T1 = [s: string]

type T2 = Parameters<<T>(arg: T) = > T>;
// type T2 = [arg: unknown]

type T3 = Parameters<typeof f1>;
// type T3 = [arg: {
// a: number;
// b: string;
// }]

type T4 = Parameters<any>;
// type T4 = unknown[]

type T5 = Parameters<never>;
// type T5 = never
Copy the code

The source code is as follows:

type Parameters<T extends(... args:any) = >any> = T extends(... args: infer P) =>any ? P : never;
Copy the code

ConstructorParameters<T>

Generates a tuple or array of constructor arguments. If T is not a constructor, return nerver

class Fn {
    constructor(name: string, age: number){}}type A = ConstructorParameters<typeof Fn> // [name:string,age:number]
                               
type T0 = ConstructorParameters<ErrorConstructor>; //[message?: string]
type T1 = ConstructorParameters<FunctionConstructor>; //string[]
Copy the code
type ConstructorParameters<T extends new(... args:any) = >any> = T extends new(... args: infer P) =>any ? P : never;
Copy the code

ReturnType<T>

Generates a type consisting of the return value of function T. Work with typeof to get the return value typeof a function

type T0 = ReturnType<() = > string>; // string
type T1 = ReturnType<(s: string) = > void>; // void
type T2 = ReturnType<<T>() = > T>;//unknown
type T3 = ReturnType<<T extends U, U extends number[] >() = > T>;//number[]
function fn(){
    return{
        name:'xiaoping'.age:18}}type T4 = ReturnType<typeof fn>;
// type T4 = {
// name: string;
// age: number;
// }
type T5 = ReturnType<any>; // any
type T6 = ReturnType<never>; // never
Copy the code

The source code is as follows:

type ReturnType<T extends(... args:any) = >any> = T extends(... args:any) => infer R ? R : any;
Copy the code

InstanceType<T>

Gets the instance type of the constructor.

class C {
    x = 0;
    y = 0;
}
type T20 = InstanceType<typeof C>;  // C
type T21 = InstanceType<any>;  // any
type T22 = InstanceType<never>;  // any
Copy the code

The source code is as follows:

type InstanceType<T extends new(... args:any) = >any> = T extends new(... args:any) => infer R ? R : any;
Copy the code

ThisParameterType<T>

Before we look at this type alias, let’s take a look at JavaScript’s weird this problem.

window.name = "hello world";
let person = {
  name: "xiaoping".say: function () {
    console.log(`my name is The ${this.name}`); }}; person.say();//my name is xiaoping
const say = person.say;
say(); //my name is hello world
Copy the code

Since this in JavaScript always points to the top of the JS execution stack, when we save the say function with a variable, the current this will fail and get unexpected results. TypeScript adds a qualification for this. An error is raised when the function executes this not as expected, as follows:

interface Person {
    name: string.say: (this: Person) = > void
}
const person: Person = {
    name: 'xiaoping'.say: function () {
        console.log(`my name is The ${this.name}`)
    }
}
person.say()
const say = person.say
// say() // // error The 'this' context of type 'void' is not assignable to method's 'this' of type 'Person'.
Copy the code

ThisParameterType is the function that gets this

function toHex(this: number) {
  return this.toString(16);
}
type ToHexThis = ThisParameterType<typeof toHex> // number
                            
function fn(value: string) {
    console.log(value)
}
type A = ThisParameterType<typeof fn> //unknown
Copy the code

The source code is as follows:

type ThisParameterType<T> = T extends (this: infer U, ... args:any[]) = >any ? U : unknown;
Copy the code

OmitThisParameter<T>

If a function is qualified for this, return the function type with this removed.

function toHex(this: number) {
    return this.toString(16);
}

type ToHexThis = ThisParameterType<typeof toHex> // number
type T1 = OmitThisParameter<typeof toHex> // () => string
type T2 = typeof toHex // (this: number) => string

const fiveToHex: OmitThisParameter<typeof toHex> = toHex.bind(5);
console.log(fiveToHex());
Copy the code

The source code is as follows:

type OmitThisParameter<T> = unknown extends ThisParameterType<T> ? T : T extends(... args: infer A) => infer R ?(. args: A) = > R : T;
Copy the code

ThisType<T>

It does not return any converted type, but rather serves as an identifier for the object literal context this, and to use this type, you need to enable configuration -noIMPLicitThis. The official example is as follows:

typeObjectDescriptor<D, M> = { data? : D; methods? : M & ThisType<D & M>;// Type of 'this' in methods is D & M
};

function makeObject<D.M> (desc: ObjectDescriptor<D, M>) :D & M {
  let data: object = desc.data || {};
  let methods: object = desc.methods || {};
  return{... data, ... methods }as D & M;
}

let obj = makeObject({
  data: { x: 0.y: 0 },
  methods: {
    moveBy(dx: number, dy: number) {
      this.x += dx; // Strongly typed this
      this.y += dy; // Strongly typed this,}}}); obj.x =10;
obj.y = 20;
obj.moveBy(5.5);
Copy the code

The implementation of the source code simply defines an empty interface.

interface ThisType<T> { }
Copy the code

The built-in string operation type

  • Uppercase<S>
  • Lowercase<S>
  • Capitalize<S>
  • Uncapitalize<S>

To strictly qualify string operations, TypeScript has built-in string types that qualify strings.

type Cases<T extends string> = `${Uppercase<T>} ${Lowercase<T>} ${Capitalize<T>} ${Uncapitalize<T>}`;
type T11 = Cases<'bar'>; // 'BAR bar Bar bar'
Copy the code

Useful but overlooked methods in TypeScript

Constant enumeration

In TypeScript, enumerations are compiled into self-executing functions like this:

enum Direction {
  Up = 1,
  Down,
  Left,
  Right,
}
Copy the code
"use strict";
var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 1] = "Up";
    Direction[Direction["Down"] = 2] = "Down";
    Direction[Direction["Left"] = 3] = "Left";
    Direction[Direction["Right"] = 4] = "Right";
})(Direction || (Direction = {}));
Copy the code

This adds a lot of redundant code and adds a value fetching process when the value is fetched. Constant enumerations are completely removed at compile time to avoid additional overhead. As follows:

 const enum Direction {
  Up = 1,
  Down,
  Left,
  Right,
}
let a = Direction.Up
Copy the code
"use strict";
let a = 1 /* Up */;
Copy the code

Merge classes and interfaces

Extend the instance type of a class through an interface declaration. Class constructor objects are not modified

declare class Foo {
    public x : number;
}

interface Foo {
    y : string;
}

function bar(foo : Foo)  {
    foo.x = 1; // No problem, there is a declaration in class Foo
    foo.y = "1"; // No problem, there is a declaration in interface Foo
}
Copy the code

Enhance existing modules

Enhance existing modules with declare Module “foo” {}. Examples on the official website are as follows:

/ observable.ts
export class Observable<T> {
    // ...
}

// map.ts
import { Observable } from "./observable";
/ / expansion ". / observables"
declare module "./observable" {

    // Expand the definition of the 'Observable' class with interface merge
    interface Observable<T> {
        map<U>(proj: (el: T) = > U): Observable<U>;
    }

}
Observable.prototype.map = / *... * /;

// consumer.ts
import { Observable } from "./observable";
import "./map";

let o: Observable<number>;
o.map(x= > x.toFixed());
Copy the code

Similarly, global scope can be extended from a module through a Declare Global declaration

// Ensure this is treated as a module.
export {};
declare global {
  interface Array<T> {
    mapToNumbers(): number[]; }}Array.prototype.mapToNumbers = function () {
  / *... * /
};
Copy the code

Later in the learning process, continue to supplement.

reference

TypeScript Utility Types