Common knowledge and skills of Typescript type

Typescript type system

This paper is mainly organized and translated from lib es5. d. TS, Microsoft Typescript documents and MDN documents.

Email: 291148484@163.com
CSDN home page: https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343
Address: https://blog.csdn.net/qq_28550263/article/details/123173417
catalogue

1. Daily type

2. Use the type keyword to define the type alias

3. Use the interface keyword to define the interface

4. Create type from type

5. Other predefined types

1. Daily type

1.1 string

String indicates the type of string value, such as "Hello, world".
A string beginning with a lowercase letter represents a string type in the JavaScript system, corresponding to the constructor (function) string represented by an uppercase letter. The TypeScript interface of string is as follows:

interface String {
    /** Returns the string representation of a string. */
    toString(): string;
    
    /**
     * Returns the character at the specified index.
     * @param pos Zero based index of the desired character.
     */
    charAt(pos: number): string;
    
    /**
     * Returns the Unicode value of the character at the specified position.
     * @param index Zero based index of the desired character. If there are no characters at the specified index, NaN is returned.
     */
    charCodeAt(index: number): number;
    
    /**
     * Returns a string containing two or more strings concatenated.
     * @param strings The string to append to the end of the string.
     */
    concat(...strings: string[]): string;
    
    /**
     * Returns the first occurrence of a substring.
     * @param searchString Substring to search in string
     * @param position Start searching the index of the String object. If omitted, the search starts at the beginning of the String.
     */
    indexOf(searchString: string, position?: number): number;
    
    /**
     * Returns the last occurrence of a substring in a string.
     * @param searchString Substring to search.
     * @param position Index to start the search. If omitted, the search starts at the end of the string.
     */
    lastIndexOf(searchString: string, position?: number): number;
    
    /**
     * Determines whether the two strings are equivalent in the current locale.
     * @param that The string to compare with the target string
     */
    localeCompare(that: string): number;
    
    /**
     * Matches a string with a regular expression and returns an array containing search results.
     * @param regexp Variable names or string literals that contain regular expression patterns and flags.
     */
    match(regexp: string | RegExp): RegExpMatchArray | null;
    
    /**
     * Replace the text in the string with a regular expression or search string.
     * @param searchValue The string to search for.
     * @param replaceValue A string containing the text to be replaced each time a searchValue is successfully matched in this string.
     */
    replace(searchValue: string | RegExp, replaceValue: string): string;
    
    /**
     * Replace the text in the string with a regular expression or search string.
     * @param searchValue The string to search for.
     * @param replacer Function that returns replacement text.
     */
    replace(searchValue: string | RegExp, replacer: (substring: string, ...args: any[]) => string): string;
    
    /**
     * Find the first matching substring in a regular expression search.
     * @param regexp Regular expression patterns and applicable flags.
     */
    search(regexp: string | RegExp): number;
    
    /**
     * Returns part of a string.
     * @param start stringObj The index at the beginning of the specified part of the.
     * @param end stringObj Specifies the index at the end of the section. The substring includes characters, but does not include the characters indicated by end. If this value is not specified, the substring continues to the end of stringObj.
     */
    slice(start?: number, end?: number): string;
    
    /**
     * Splits a string into substrings using the specified delimiter and returns them as an array.
     * @param separator Identifies a string that separates one or more characters of a string. If omitted, a single element array containing the entire string is returned.
     * @param limit A value that limits the number of elements returned in the array.
     */
    split(separator: string | RegExp, limit?: number): string[];
    
    /**
     * Returns a substring at a specified position in a string object.
     * @param start A zero based index number indicating the beginning of a substring.
     * @param end A zero based index number indicating the end of the substring. The substring includes characters, but does not include the characters indicated by end.
     * If end is omitted, characters from the beginning to the end of the original string are returned.
     */
    substring(start: number, end?: number): string;
    
    /** Converts all alphabetic characters in a string to lowercase. */
    toLowerCase(): string;
    
    /** Convert all alphabetic characters to lowercase considering the current locale of the host environment. */
    toLocaleLowerCase(locales?: string | string[]): string;
    
    /** Converts all alphabetic characters in a string to uppercase. */
    toUpperCase(): string;
    
    /** Returns a string in which all alphabetic characters have been converted to uppercase, depending on the current locale of the host environment. */
    toLocaleUpperCase(locales?: string | string[]): string;
    
    /** Removes leading and trailing spaces and line terminators from the string. */
    trim(): string;
    
    /** Returns the length of a string object. */
    readonly length: number;
    
    // IE extension
    /**
     * Gets a substring that starts at the specified location and has the specified length.
     * @deprecated Traditional features of browser compatibility
     * @param from The starting position of the required substring. The index of the first character in the string is zero.
     * @param length The number of characters contained in the returned string.
     */
    substr(from: number, length?: number): string;

    /** Returns the original value of the specified object. */
    valueOf(): string;

    readonly [index: number]: string;
}

interface StringConstructor {
    new(value?: any): String;
    (value?: any): string;
    readonly prototype: String;
    fromCharCode(...codes: number[]): string;
}

/**
 * Allows you to manipulate and format text strings and to determine and locate substrings in strings.
 */
declare var String: StringConstructor;

1.2 number

For example 42 JavaScript has no special runtime value for integers, so it is not equivalent to int or float - everything is just a number type.
The number beginning with lowercase letter represents the number type in the JavaScript system, which corresponds to the constructor (function) number represented by uppercase letter. The TypeScript interface of number is as follows:

interface Number {
    /**
     * Returns the string representation of the object.
     * @param radix Specifies the cardinality used to convert a numeric value to a string. This value is for numbers only.
     */
    toString(radix?: number): string;

    /**
     * Returns a string representing a number in fixed-point notation.
     * @param fractionDigits The number of digits after the decimal point. Must be in the range of 0 - 20, including 0 and 20.
     */
    toFixed(fractionDigits?: number): string;

    /**
     * Returns a string containing a number represented by an exponent.
     * @param fractionDigits The number of digits after the decimal point. Must be in the range of 0 - 20, including 0 and 20.
     */
    toExponential(fractionDigits?: number): string;

    /**
     * Returns a string containing a number represented by an exponent or fixed-point representation of a specified number of digits.
     * @param precision Number of significant digits. Must be in the range of 1 - 21, including 1 and 21.
     */
    toPrecision(precision?: number): string;

    /** Returns the original value of the specified object. */
    valueOf(): number;
}

interface NumberConstructor {
    new(value?: any): Number;
    (value?: any): number;
    readonly prototype: Number;

    /** JavaScript The maximum number that can be represented in. Approximately equal to 1.79E+308. */
    readonly MAX_VALUE: number;

    /** JavaScript The number closest to zero that can be represented in. Approximately equal to 5.00E-324. */
    readonly MIN_VALUE: number;

    /**
     * Is not a numeric value.
     * In an equality comparison, NaN is not equal to any value, including itself. To test whether a value is equivalent to NaN, use the isNaN function.
     */
    readonly NaN: number;
    
    /**
     * A value that is less than the maximum negative number that can be represented in JavaScript.
     * JavaScript Will be negative_ The infinity value is displayed as - infinity.
     */
    readonly NEGATIVE_INFINITY: number;

    /**
     * A value greater than the maximum number that can be represented in JavaScript.
     * JavaScript Will be positive_ INFINITY value is displayed as INFINITY.
     */
    readonly POSITIVE_INFINITY: number;
}

/** An object that represents any type of number. All JavaScript numbers are 64 bit floating point numbers. */
declare var Number: NumberConstructor;

1.3 boolean

A type consisting of only two values, true and false.
Booleans beginning with lowercase letters represent Boolean types in the JavaScript system, corresponding to the constructor (function) Boolean represented by uppercase letters. The TypeScript interface of Boolean is as follows:

interface Boolean {
    /** Returns the original value of the specified object. */
    valueOf(): boolean;
}

interface BooleanConstructor {
    new(value?: any): Boolean;
    <T>(value?: T): boolean;
    readonly prototype: Boolean;
}

declare var Boolean: BooleanConstructor;

1.4 array

To specify an array like type [1, 2, 3], use the syntax number []; This syntax applies to any type (such as string [], string array, etc.).
The array starting with lowercase letters represents the array type in the JavaScript system, which corresponds to the constructor (function) array represented by uppercase letters. The TypeScript interface of array is as follows:

interface Array<T> {
    /**
     * Gets Or the length of the sets array. This is one number higher than the highest index in the array.
     */
    length: number;
    /**
     * Returns a string representation of an array.
     */
    toString(): string;
    /**
     * Returns a string representation of an array. Use their toLocaleString method to convert elements to strings.
     */
    toLocaleString(): string;
    /**
     * Removes the last element from the array and returns it.
     * If the array is empty, undefined is returned and the array is not modified.
     */
    pop(): T | undefined;
    /**
     * Appends a new element to the end of the array and returns the new length of the array.
     * @param items The new element to add to the array.
     */
    push(...items: T[]): number;
    /**
     * Combine two or more arrays.
     * This method returns a new array without modifying any existing arrays.
     * @param items Other arrays and / or items to add to the end of the array.
     */
    concat(...items: ConcatArray<T>[]): T[];
    /**
     * Combine two or more arrays.
     * This method returns a new array without modifying any existing arrays.
     * @param items Other arrays and / or items to add to the end of the array.
     */
    concat(...items: (T | ConcatArray<T>)[]): T[];
    /**
     * Adds all elements in the array to a string, separated by the specified separator string.
     * @param separator A string used to separate one element in the array from the next element in the result string. If omitted, array elements are separated by commas.
     */
    join(separator?: string): string;
    /**
     * Invert elements in an array in place.
     * This method mutates the array and returns a reference to the same array.
     */
    reverse(): T[];
    /**
     * Removes the first element from the array and returns it.
     * If the array is empty, undefined is returned and the array is not modified.
     */
    shift(): T | undefined;
    /**
     * Returns a copy of a portion of an array.
     * For start and end, you can use a negative index to indicate the offset from the end of the array.
     * For example, - 2 represents the penultimate element of the array.
     * @param start The start index of the specified part of the array.
     * If start undefined, the slice starts at index 0.
     * @param end The end index of the specified part of the array. This does not include elements at index 'end'.
     * If end undefined, the slice is extended to the end of the array.
     */
    slice(start?: number, end?: number): T[];
    /**
     * Sort arrays in place.
     * This method mutates the array and returns a reference to the same array.
     * @param compareFn A function used to determine the order of elements. If the first parameter is less than the second parameter, it will return a negative value. If the two parameters are equal, it will return zero. Otherwise, it will return a positive value. If omitted, the elements are sorted in ascending ASCII character order.
     * ```ts
     * [11,2,22,1].sort((a, b) => a - b)
     * ```
     */
    sort(compareFn?: (a: T, b: T) => number): this;
    /**
     * Remove elements from the array, insert new elements in their positions if necessary, and return the deleted elements.
     * @param start The position in the array where the element is removed from zero.
     * @param deleteCount Number of elements to remove.
     * @returns An array containing deleted elements.
     */
    splice(start: number, deleteCount?: number): T[];
    /**
     * Remove elements from the array, insert new elements in their positions if necessary, and return the deleted elements.
     * @param start The position in the array where the element is removed from zero.
     * @param deleteCount Number of elements to remove.
     * @param items The element to be inserted into the array in place of the deleted element.
     * @returns An array containing deleted elements.
     */
    splice(start: number, deleteCount: number, ...items: T[]): T[];
    /**
     * Inserts a new element at the beginning of the array and returns the new length of the array.
     * @param items The element to insert at the beginning of the array.
     */
    unshift(...items: T[]): number;
    /**
     * Returns the index of the first occurrence of a value in the array. If it does not exist, it returns - 1.
     * @param searchElement The value to locate in the array.
     * @param fromIndex Index of the array to start the search. If fromIndex is omitted, the search starts at index 0.
     */
    indexOf(searchElement: T, fromIndex?: number): number;
    /**
     * Returns the index of the last occurrence of the specified value in the array. If it does not exist, it returns - 1.
     * @param searchElement The value to locate in the array.
     * @param fromIndex The index of the array that starts the backward search. If fromIndex is omitted, the search starts at the last index in the array.
     */
    lastIndexOf(searchElement: T, fromIndex?: number): number;
    /**
     * Determines whether all members of the array meet the specified test.
     * @param predicate A function that accepts up to three arguments. 
     * every Method calls the predicate function for each element in the array until the predicate returns a value that can be forced to Boolean false, or until the end of the array.
     * @param thisArg this Keyword an object that can be referenced in a predicate function.
     * If thisArg is omitted, use undefined as the value of this.
     */
    every<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): this is S[];
    /**
     * Determines whether all members of the array meet the specified test.
     * @param predicate A function that accepts up to three arguments. The every method calls the predicate function for each element in the array,
     * Until the predicate returns a value that can be forced to Boolean false, or until the end of the array.
     * @param thisArg this Keyword an object that can be referenced in a predicate function.
     * If thisArg is omitted, use undefined as the value of this.
     */
    every(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean;
    /**
     * Determines whether the specified callback function returns true for any element in the array.
     * @param predicate A function that accepts up to three arguments. The some method calls the predicate function for each element in the array,
     * Until the predicate returns a value that can be forced to Boolean true, or until the end of the array.
     * @param thisArg this Keyword an object that can be referenced in a predicate function.
     * If thisArg is omitted, use undefined as the value of this.
     */
    some(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean;
    /**
     * Performs the specified operation on each element in the array.
     * @param callbackfn  A function that accepts up to three arguments. forEach calls the callbackfn function once for each element in the array.
     * @param thisArg  this Keyword the object that can be referenced in the callback FN function. If thisArg is omitted, use undefined as the value of this.
     */
    forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
    /**
     * Call the defined callback function on each element of the array and return the array containing the result.
     * @param callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function once for each element in the array.
     * @param thisArg this Keyword the object that can be referenced in the callback FN function. If thisArg is omitted, use undefined as the value of this.
     */
    map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
    /**
     * Returns the array elements that meet the conditions specified in the callback function.
     * @param predicate A function that accepts up to three arguments. The filter method calls the predicate function once for each element in the array.
     * @param thisArg this Keyword an object that can be referenced in a predicate function. If thisArg is omitted, use undefined as the value of this.
     */
    filter<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
    /**
     * Returns the array elements that meet the conditions specified in the callback function.
     * @param predicate A function that accepts up to three arguments. The filter method calls the predicate function once for each element in the array.
     * @param thisArg this Keyword an object that can be referenced in a predicate function. If thisArg is omitted, use undefined as the value of this.
     */
    filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): T[];
    /**
     * Calls the specified callback function for all elements in the array. The return value of the callback function is the result of accumulation and is provided as a parameter the next time the callback function is called.
     * @param callbackfn A function that accepts up to four parameters. The reduce method calls the callbackfn function once for each element in the array.
     * @param initialValue If initialValue is specified, it will be used as the initial value to start accumulation. The first call to the callbackfn function provides this value as an argument rather than an array value.
     */
    reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
    reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
    /**
     * Calls the specified callback function for all elements in the array. The return value of the callback function is the result of accumulation and is provided as a parameter the next time the callback function is called.
     * @param callbackfn A function that accepts up to four parameters. The reduce method calls the callbackfn function once for each element in the array.
     * @param initialValue If initialValue is specified, it will be used as the initial value to start accumulation. The first call to the callbackfn function provides this value as an argument rather than an array value.
     */
    reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
    /**
     * Calls the specified callback function for all elements in the array in descending order. The return value of the callback function is the result of accumulation and is provided as a parameter the next time the callback function is called.
     * @param callbackfn A function that accepts up to four parameters. The reduceRight method calls the callbackfn function once for each element in the array.
     * @param initialValue If initialValue is specified, it will be used as the initial value to start accumulation. The first call to the callbackfn function provides this value as an argument rather than an array value.
     */
    reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
    reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
    /**
     * Calls the specified callback function for all elements in the array in descending order. The return value of the callback function is the result of accumulation and is provided as a parameter the next time the callback function is called.
     * @param callbackfn A function that accepts up to four parameters. The reduceRight method calls the callbackfn function once for each element in the array.
     * @param initialValue If initialValue is specified, it will be used as the initial value to start accumulation. The first call to the callbackfn function provides this value as an argument rather than an array value.
     */
    reduceRight<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;

    [n: number]: T;
}

interface ArrayConstructor {
    new(arrayLength?: number): any[];
    new <T>(arrayLength: number): T[];
    new <T>(...items: T[]): T[];
    (arrayLength?: number): any[];
    <T>(arrayLength: number): T[];
    <T>(...items: T[]): T[];
    isArray(arg: any): arg is any[];
    readonly prototype: any[];
}

declare var Array: ArrayConstructor;

1.5 object type

This refers to any JavaScript value with attributes, almost all attributes! To define an object type, we only need to list its properties and their types. To define an object type, we only need to list its properties and their types.
For example, function:

// The parameter's type annotation is an object type
function printCoord(pt: { x: number; y: number }) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 3, y: 7 });

Here, we annotate the parameters - x and y - with a type with two attributes, both of which are type number. You can use, or; Separate attributes, and the last separator is optional.

The type part of each attribute is also optional. If no type is specified, it is assumed to be any Type.

optional attribute

Object types can also specify that some or all of their properties are optional. To do this, please add a? number:

function printName(obj: { first: string; last?: string }) {
  // ...
}
// Both OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });

1.6 any type

any is a special type that you can use when you don't want a particular value to cause a type check error. any is useful when you don't want to write a long type just to convince TypeScript that a particular line of code is OK.

1.7 joint type

TypeScript's type system allows you to use various operators to build new types from existing types. A union type is a type that consists of two or more other types and represents a value that may be any of these types. We refer to each of these types as a member of a federation. For example:

function printId(id: number | string) {
  console.log("Your ID is: " + id);
}

1.8 type assertion

Sometimes you get information about value types that TypeScript cannot know.

For example, if you are using document Getelementbyid, TypeScript only knows that this will return some kind of HTMLElement, but you may know that your page will always have HTMLCanvasElement with the given ID.

In this case, you can use type assertions to specify more specific types:

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

Like type annotations, type assertions are removed by the compiler and do not affect the runtime behavior of the code.

You can also use angle bracket syntax (unless the code is in a. tsx file), which is equivalent:

const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");

Because type assertions are deleted at compile time, there are no run-time checks associated with type assertions. null if the type assertion is wrong, there will be no exception or build.

TypeScript only allows type assertions to be converted to more specific or less specific versions of types. This rule prevents "impossible" coercion, such as:

const x = "hello" as number;

Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

Sometimes, this rule may be too conservative and does not allow more complex casts that may be effective. If this happens, you can use two assertions, first any (or unknown, which we'll cover later), and then the desired type:

const a = (expr as any) as T;

1.9 enumeration

Enumeration is a function that TypeScript adds to JavaScript. It is not a type level extension of JavaScript. Enumeration allows you to describe a value that may be one of a set of possible named constants. Unlike most TypeScript features, this is not a type level addition to JavaScript, but something added to the language and runtime. Because of this, this is a function that you should know exists, but it may be delayed unless you are sure.

1.9.1 numerical enumeration

We'll start with numerical enumeration, which you might be more familiar with if you're from another language. enum keyword can be used to define enumeration.

enum Direction {
  Up = 1,
  Down,
  Left,
  Right,
}

Above, we have an up enumeration 1 with the initialized number. From then on, all of the following members will be automatically incremented. In other words, direction Up has a value of 1, Down has 2, Left has 3 and Right has 4.

If we like, we can completely eliminate the initializer:

enum Direction {
  Up,
  Down,
  Left,
  Right,
}

Using enumeration is simple: just access any member as an attribute of the enumeration itself and declare the type with the name of the enumeration:

enum UserResponse {
  No = 0,
  Yes = 1,
}
 
function respond(recipient: string, message: UserResponse): void {
  // ...
}
 
respond("Princess Caroline", UserResponse.Yes);

Enumerations without initializers either need to come first, or they must follow numeric enumerations initialized with numeric constants or other constant enumeration members. In other words, the following are not allowed:

enum E {
  A = getSomeValue(),
  B,
}

Enum member must have initializer.

1.9.2 string enumeration

String enumeration is a similar concept, but there are some subtle runtime differences, as described below. In string enumeration, each member must be initialized with a string literal or another string enumeration member.

enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT",
}

Although string enumerations do not have automatic increment behavior, the advantage of string enumerations is that they can be well "serialized". In other words, if you are debugging and have to read the runtime value of a numeric enumeration, the value is usually opaque -- it doesn't convey any useful meaning by itself (although reverse mapping usually helps). String enumeration allows you to provide meaningful and readable values at code run time, regardless of the name of the enumeration member itself.

1.9.3 heterogeneous enumeration

Technically, enumerations can be mixed with string and numeric members, but it's not clear why you do this:

enum BooleanLikeHeterogeneousEnum {
  No = 0,
  Yes = "YES",
}

Unless you really want to take advantage of JavaScript's runtime behavior in a clever way, it's not recommended.

1.9.4 calculation members and constant members

Each enumeration member has a value associated with it, which can be a constant or a calculated value. Enumeration members are considered constants when:

  • It is the first member in the enumeration and has no initializer, in which case it is assigned a value of 0:
// E.X is constant:
enum E {
  X,
}
  • It has no initializer, and the previous enumeration member is a numeric constant. In this case, the value of the current enumeration member will be the value of the previous enumeration member plus one.
// All enum members in 'E1' and 'E2' are constant.
 
enum E1 {
  X,
  Y,
  Z,
}
 
enum E2 {
  A = 1,
  B,
  C,
}
  • Enumeration members are initialized with constant enumeration expressions. Constant enumeration expressions are a subset of TypeScript expressions that can be fully evaluated at compile time. An expression is a constant enumeration expression. If it is:

    • Text enumeration expressions (basically string text or numeric text)
    • A reference to a previously defined constant enumeration member (which can originate from a different enumeration)
    • Parenthesized constant enumeration expression
    • One of the +, -, unary operators applied to constant enumeration expressions~
    • +, -, *, /,%, <, > >, > >, &, |, ^ binary operators with constant enumeration expressions as operands

    Evaluating a constant enumeration expression as NaNor is a compile time error Infinity.

In all other cases, enumeration members are considered computed.

enum FileAccess {
  // constant members
  None,
  Read = 1 << 1,
  Write = 1 << 2,
  ReadWrite = Read | Write,
  // computed member
  G = "123".length,
}

1.9.5 joint enumeration and enumeration member types

There is a special subset of uncomputed constant enumeration members: literal enumeration members. A literal enumeration member is a constant enumeration member that does not have an initialization value, or has a value initialized to

  • Any string literal (e.g. "foo", "bar", "baz")
  • Any numeric text (e.g. 1, 100)
  • A unary minus sign applied to any numeric text (for example, - 1, - 100)

When all members in the enumeration have literal enumeration values, some special semantics will come into play.
First, enumeration members have also become types! For example, we can say that some members can only have the values of enumerated members:

enum ShapeKind {
  Circle,
  Square,
}
 
interface Circle {
  kind: ShapeKind.Circle;
  radius: number;
}
 
interface Square {
  kind: ShapeKind.Square;
  sideLength: number;
}
 
let c: Circle = {
  kind: ShapeKind.Square,
Type 'ShapeKind.Square' is not assignable to type 'ShapeKind.Circle'.
  radius: 100,
};

Another change is that the enumeration type itself effectively becomes the union of each enumeration member. Using joint enumeration, the type system can take advantage of the fact that it knows the exact set of values that exist in the enumeration itself. Because of this, TypeScript can catch errors that we may incorrectly compare values. For example:

enum E {
  Foo,
  Bar,
}
 
function f(x: E) {
  if (x !== E.Foo || x !== E.Bar) {
    //
  }
}

This condition will always return 'true' since the types 'E.Foo' and 'E.Bar' have no overlap.
In that example, we first check whether Xi is not E.Foo. If the check is successful, then our | will short circuit and the main body of 'if' will run. However, if the check is unsuccessful, it can only be x, so E.Foo will see if it is equal to meaningless E.Bar.

1.9.6 runtime enumeration

Enumerations are real objects that exist at runtime. For example, the following enumeration

enum E {
  X,
  Y,
  Z,
}

Can actually be passed to a function

enum E {
  X,
  Y,
  Z,
}
 
function f(obj: { X: number }) {
  return obj.X;
}
 
// Works, since 'E' has a property named 'X' which is a number.
f(E);

1.9.7 enumeration at compile time

Although enumerations are real objects that exist at runtime, the keyof keyword works differently than you would expect for a typical object. Instead, use keyof typeof to get the type that represents all Enum keys as strings.

enum LogLevel {
  ERROR,
  WARN,
  INFO,
  DEBUG,
}
 
/**
 * This is equivalent to:
 * type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
 */
type LogLevelStrings = keyof typeof LogLevel;
 
function printImportant(key: LogLevelStrings, message: string) {
  const num = LogLevel[key];
  if (num <= LogLevel.WARN) {
    console.log("Log level key is:", key);
    console.log("Log level value is:", num);
    console.log("Log level message is:", message);
  }
}
printImportant("ERROR", "This is a message");

Reverse mapping

In addition to creating objects with attribute names for members, numeric enumeration members also get a reverse mapping from enumeration values to enumeration names. For example, in this example:

enum Enum {
  A,
}
 
let a = Enum.A;
let nameOfA = Enum[a]; // "A"

TypeScript compiles it into the following JavaScript:

"use strict";
var Enum;
(function (Enum) {
    Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
let a = Enum.A;
let nameOfA = Enum[a]; // "A"

In this generated code, the enumeration is compiled into an object that stores forward (name - > value) and reverse (value - > name) mappings. References to other enumeration members are always issued as property access and are never inlined.

Remember that string enumeration members do not generate reverse mappings at all.

const enumeration

In most cases, enumeration is a completely effective solution. However, sometimes the requirements are more stringent. To avoid paying extra generated code and additional overhead costs when accessing enumerated values, const enumeration can be used. Constant enumeration is defined using the const modifier on the enumeration:

const enum Enum {
  A = 1,
  B = A * 2,
}

Constant enumerations can only use constant enumeration expressions, and unlike regular enumerations, they are completely deleted during compilation. Constant enumeration members are inline in the use site. It is possible that const members cannot be evaluated.

const enum Direction {
  Up,
  Down,
  Left,
  Right,
}
 
let directions = [
  Direction.Up,
  Direction.Down,
  Direction.Left,
  Direction.Right,
];

In the generated code, it will become:

"use strict";
let directions = [
    0 /* Up */,
    1 /* Down */,
    2 /* Left */,
    3 /* Right */,
];
Constant enumeration trap

Inline enumeration values are simple at first, but can have a subtle impact. These traps are only related to the environment const enumeration (basically the const enumeration in the. d.ts file) and sharing them between projects, but if you are publishing or using d. TS file, these traps may apply to you, because tsc --declaration will TS file is converted to d. TS file.

isolatedModules this mode is fundamentally incompatible with environment constant enumeration for the reasons listed in the documentation. This means that if you publish environment constant enumerations, downstream consumers will not be able to use these enumerations at the same time.
You can easily inline values from version A of A dependency at compile time and import version b at run time. Enumerations in versions A and B can have different values. If you are not careful, it will lead to surprising errors if, such as using wrong statement branches. These errors are particularly harmful because automated tests are usually run at the same time as the project is built, with the same dependent version, and these errors are completely ignored.
Importsnotusedasvalues: 'preserve' does not ignore the import of const enumeration used as a value, but the environment const enumeration does not guarantee runtime js file exists. Unresolved imports can cause errors at run time. At present, const enum values is not allowed for common methods that explicitly omit import (type import only).
Here are two ways to avoid these pitfalls:

A. Do not use const enumeration at all. You can easily disable const enumeration with the help of linter. Obviously, this avoids any problems with const enumerations, but prevents your project from inline its own enumerations. Different from the inline enumeration of other projects, the inline project has no problem with its own enumeration and will affect the performance. B. Do not publish environment constant enumeration, in preserveConstEnums This is the method used internally by the TypeScript project itself. preserveConstEnums issues the same JavaScript for const enumeration as normal enumeration. You can then safely strip the const modifier from the file during the build step d.ts

In this way, downstream consumers will not inline enumeration from your project to avoid the above trap, but the project can still inline its own enumeration, which is different from completely prohibiting const enumeration.

1.9.8 environment enumeration

The environment enumeration is used to describe the shape of an enumeration type that already exists.

declare enum Enum {
  A = 1,
  B,
  C = 2,
}

An important difference between environment enumerations and non environment enumerations is that in conventional enumerations, if the previous enumeration member is considered constant, then the member without initializer will be considered constant. In contrast, environment (and non constant) enumeration members without initializers are always considered calculated.

1.9.9 objects and enumerations

as const in modern TypeScript, when an object is sufficient, you may not need to enumerate:

const enum EDirection {
  Up,
  Down,
  Left,
  Right,
}
 
const ODirection = {
  Up: 0,
  Down: 1,
  Left: 2,
  Right: 3,
} as const;
 
// EDirection.Up;
           
(enum member) EDirection.Up = 0
 
// ODirection.Up;
           
// (property) Up: 0
 
// Using the enum as a parameter
function walk(dir: EDirection) {}
 
// It requires an extra line to pull out the values
type Direction = typeof ODirection[keyof typeof ODirection];
function run(dir: Direction) {}
 
walk(EDirection.Left);
run(ODirection.Right);

The biggest argument enum for supporting this format instead of TypeScript is that it keeps your code base consistent with the state of JavaScript, and you can turn to other syntax when / if you add enumerations to JavaScript.

2. Use the type keyword to define the type alias

We have been using them by writing object types and union types directly in type annotations. This is convenient, but you usually want to use the same type multiple times and reference it with a name. That's the type alias - the name of any type. The syntax of a type alias is:

type Point = {
  x: number;
  y: number;
};
 
// Exactly the same as the earlier example
function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
 
printCoord({ x: 100, y: 100 });

In fact, you can use type aliases to name any type, not just object types. For example, a type alias can name a union type:

type ID = number | string;

Note that aliases are just aliases - you cannot use type aliases to create different / different versions of the same type. When you use an alias, it's as if you've written the alias type. In other words, this code may look illegal, but it is OK according to TypeScript, because both types are aliases of the same type:

type UserInputSanitizedString = string;
 
function sanitizeInput(str: string): UserInputSanitizedString {
  return sanitize(str);
}
 
// Create a sanitized input
let userInput = sanitizeInput(getInput());
 
// Can still be re-assigned with a string though
userInput = "new input";

3. Use the interface keyword to define the interface

3.1 interface

Interface declaration is another way to name object types:

interface Point {
  x: number;
  y: number;
}
 
function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
 
printCoord({ x: 100, y: 100 });

Just as we used type aliases above, this example works as if we used anonymous object types. TypeScript only cares about the structure printCoord of the value we pass in -- it only cares about whether it has the expected properties. Focusing only on the structure and function of types is why we call TypeScript a structure type system.

3.2 difference between type definition and interface definition

Type aliases are very similar to interfaces, and in many cases you are free to choose between them. Almost all feature interfaces can use type in. The main difference is that you can't reopen the type to add new attributes, and the interface is always extensible. In most cases, you can choose according to your preferences, and TypeScript will tell you whether you need other types of declarations. If you want a heuristic, use the interface until you need to use type.

3.2.1 extension

3.2.1.1 expansion interface

interface Animal {
  name: string
}

interface Bear extends Animal {
  honey: boolean
}

const bear = getBear() 
bear.name
bear.honey

3.2.1.2 extension type (through intersection)

type Animal = {
  name: string
}

type Bear = Animal & { 
  honey: boolean 
}

const bear = getBear();
bear.name;
bear.honey;

3.2.2 modification

3.2.2.1 add new fields to the existing interface

interface Window {
  title: string
}

interface Window {
  ts: TypeScriptAPI
}

const src = 'const a = "Hello World"';
window.ts.transpileModule(src, {});

3.2.2.2 type cannot be changed after creation

type Window = {
  title: string
}

type Window = {
  ts: TypeScriptAPI
}

 // Error: Duplicate identifier 'Window'.

3.2.3 others

  • Type aliases may not participate in declaration merging, but interfaces can.
  • Interface can only be used to declare the shape of an object, and cannot rename primitives.
  • It always appears in the original message as its name, but only in the error.

4. Create type from type

4.1 generics: treat types as parameters

The ability to create a component that can work on multiple types rather than a single type. This allows users to use these components and use their own types.

function identity<Type>(arg: Type): Type {
  return arg;
}

The Type here is a Type, which can be regarded as a Type parameter passed into the function. For a more practical example, the Array constructor is the Array function. When we represent a string Array, the interface corresponds to Array < T >, that is, parentheses are used to pass in ordinary parameters and angle brackets are used to pass in Type parameters. For example, Map objects can also be used to restrict the types of keys and values. For example, Map < string, any > restricts that keys must be of string Type.
For more information on generic usage, please refer to the official documentation:
https://www.typescriptlang.org/docs/handbook/2/generics.html

4.2 keyof type operator

Operator takes the keyof object type and generates a string or numeric literal union of its keys. The following types P and "x" | "yes":

type Point = { x: number; y: number };
type P = keyof Point;

type P = keyof Point

If the type has a string or number index signature, keyof will return these types:

type Arrayish = { [n: number]: unknown };
type A = keyof Arrayish;

type A = number

type Mapish = { [k: string]: boolean };
type M = keyof Mapish;

type M = string | number

Note that in this example, M is string | number - this is because the JavaScript object key is always cast to a string, so obj[0] is always the same as obj["0"]

keyof types become particularly useful when used in conjunction with mapped types, which we will learn more about later.

4.3 Typeof operator

JavaScript already has a typeof operator that can be used in the context of expressions:

// Prints "string"
console.log(typeof "Hello world");

TypeScript adds a typeof operator, which you can use in the type context to refer to the type of variable or property:

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

let n: string

This is not very useful for basic types, but in combination with other type operators, you can use typeof to easily express many patterns. For example, let's start with the predefined type ReturnType < T >. It accepts a function type and generates its return type:

type Predicate = (x: unknown) => boolean;
type K = ReturnType<Predicate>;

type K = boolean

If we try to use ReturnType on the function name, we will see a guiding error:

function f() {
  return { x: 10, y: 3 };
}
type P = ReturnType<f>;

'f' refers to a value, but is being used as a type here. Did you mean 'typeof f'?

Remember, values and types are not the same thing. To reference the type of value f, we use typeof:

function f() {
  return { x: 10, y: 3 };
}
type P = ReturnType<typeof f>;

type P = {
x: number;
y: number;
}

limit

TypeScript intentionally limits the types of expressions you can use typeof.

Specifically, it is legal to use typeof on identifiers (i.e. variable names) or their properties. This helps avoid the confusing trap of writing code that you think is being executed but not actually executed:

// Meant to use = ReturnType<typeof msgbox>
let shouldContinue: typeof msgbox("Are you sure you want to continue?");

',' expected.

4.4 index access type

We can use the index access type to find a specific attribute of another type:

type Person = { age: number; name: string; alive: boolean };
type Age = Person["age"];

type Age = number

The index type itself is a type, so we can completely use the union keyof, or other types:

type I1 = Person["age" | "name"];

type I1 = string | number

type I2 = Person[keyof Person];

type I2 = string | number | boolean

type AliveOrName = "alive" | "name";
type I3 = Person[AliveOrName];

type I3 = string | boolean

If you try to index a property that does not exist, you may even see an error:

type I1 = Person["alve"];

Property 'alve' does not exist on type 'Person'.

Another example of indexing with any type is number, which is used to get the type of array elements. We can combine it with typeof to easily capture the element types of array literals:

const MyArray = [
  { name: "Alice", age: 15 },
  { name: "Bob", age: 23 },
  { name: "Eve", age: 38 },
];
 
type Person = typeof MyArray[number];

type Person = {
name: string;
age: number;
}

type Age = typeof MyArray[number]["age"];

type Age = number

// perhaps
type Age2 = Person["age"];

type Age2 = number

When indexing, only types can be used, which means that const cannot be used for variable reference:

const key = "age";
type Age = Person[key];

Type 'key' cannot be used as an index type. 'key' refers to a value, but is being used as a type here. Did you mean 'typeof key'?

However, you can use type aliases for refactoring of similar styles:

type key = "age";
type Age = Person[key];

4.5 condition type

At the heart of the most useful program, we must make decisions based on input. JavaScript programs are no exception, but given that values are easy to introspect, these decisions are also based on the type of input. Condition types help to describe the relationship between input and output types.

interface Animal {
  live(): void;
}
interface Dog extends Animal {
  woof(): void;
}
 
type Example1 = Dog extends Animal ? number : string;

type Example1 = number

type Example1 = Dog extends Animal ? number : string;

type Example1 = number

type Example2 = RegExp extends Animal ? number : string;

type Example2 = string

The form of condition type is a bit like the condition expression in JavaScript (condition? Trueexpression: false expression):

SomeType extends OtherType ? TrueType : FalseType;

When the type on the left of extends can be assigned to the type on the right, you will get the type in the first branch (true branch); Otherwise, you will get the type in the latter branch (false branch).

From the above example, conditional types may not be immediately useful - we can tell ourselves whether Dog extends Animal and choose number or string! But the strength of conditional types is that they are used with generics.

For example, let's use the following createLabel function:

interface IdLabel {
  id: number /* some fields */;
}
interface NameLabel {
  name: string /* other fields */;
}
 
function createLabel(id: number): IdLabel;
function createLabel(name: string): NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
  throw "unimplemented";
}

These overloads of createLabel describe a JavaScript function that makes a choice based on the input type. Please note the following:

  1. This becomes cumbersome if a library has to make the same choice over and over again in its API.
  2. We have to create three overloads: one for each case where we determine the type (one for string and one for number), and the other for the most common case (using string | number). For each new type that createLabel can handle, the number of overloads increases exponentially.

Instead, we can encode the logic as a condition type:

type NameOrId<T extends number | string> = T extends number
  ? IdLabel
  : NameLabel;

Then, we can use conditional types to simplify overloading into a function without overloading.

function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
  throw "unimplemented";
}
 
let a = createLabel("typescript");

let a: NameLabel

let b = createLabel(2.8);

let b: IdLabel

let c = createLabel(Math.random() ? "hello" : 42);

let c: NameLabel | IdLabel

Condition type constraint

Usually, the check of condition type will provide us with some new information. Just as using type protection narrowing can give us a more specific type, the real branch of conditional types will further limit the genericity of the types we check.

For example, let's take the following measures:

type MessageOf<T> = T["message"];

Type '"message"' cannot be used to index type 'T'.

In this example, the TypeScript makes an error because t doesn't know there is a name called message We can constrain T:

type MessageOf<T extends { message: unknown }> = T["message"];
 
interface Email {
  message: string;
}
 
type EmailMessageContents = MessageOf<Email>;

type EmailMessageContents = string

However, what if we want MessageOf to adopt any type, and the default is never, and a message attribute is unavailable? We can do this by introducing a constraint type:

type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;
 
interface Email {
  message: string;
}
 
interface Dog {
  bark(): void;
}
 
type EmailMessageContents = MessageOf<Email>;

type EmailMessageContents = string

type DogMessageContents = MessageOf<Dog>;

type DogMessageContents = never

Within the true branch, TypeScript knows that T will have a message attribute.

As another example, we can also write a type named Flatten, which flattens array types to their element types, but does not deal with them in other cases:

type Flatten<T> = T extends any[] ? T[number] : T;
 
// Extracts out the element type.
type Str = Flatten<string[]>;

type Str = string

// Leaves the type alone.
type Num = Flatten<number>;

type Num = number

When Flatten gives an array type, it uses the index to access number to get the element type of string []. Otherwise, it returns only the given type.

Infer in condition type

We just found ourselves using conditional types to apply constraints and then extract types. This eventually becomes a common operation, and condition types make it easier.

Conditional types provide us with a way to infer the types we compare in real branches using the infer keyword. For example, we can infer the element type, Flatten, instead of getting it "manually" using the index access type:

type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;

Here, we use the infer keyword to declaratively introduce a new generic type variable Item, instead of specifying how to retrieve the element type in the real branch. T his makes it unnecessary for us to consider how to mine and explore the structure of the types we are interested in.

We can use the keyword infer to write some useful auxiliary type aliases. For example, for simple cases, we can extract the return type from the function type:

type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
  ? Return
  : never;
 
type Num = GetReturnType<() => number>;

type Num = number

type Str = GetReturnType<(x: string) => string>;

type Str = string

type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;

type Bools = boolean[]

When you infer from a type that has multiple call signatures, such as the type of an overloaded function, you infer from the last signature (presumably the most relaxed generalization). It is not possible to make overload decisions based on a list of parameter types.

declare function stringOrNum(x: string): number;
declare function stringOrNum(x: number): string;
declare function stringOrNum(x: string | number): string | number;
 
type T1 = ReturnType<typeof stringOrNum>;

type T1 = string | number

Distributed condition type

When conditional types act on generic types, they become assignable when given a union type. For example, take the following measures:

type ToArray<Type> = Type extends any ? Type[] : never;

If we insert a union type into ToArray, the condition type will be applied to each member of the union.

type ToArray<Type> = Type extends any ? Type[] : never;
 
type StrArrOrNumArr = ToArray<string | number>;

type StrArrOrNumArr = string[] | number[]
What happens here is that StrArrOrNumArr is distributed in:

string | number;

And map each member type of the Federation to valid content:

ToArray<string> | ToArray<number>;

This leaves us with:

string[] | number[];

Typically, distributivity is the desired behavior. To avoid this behavior, you can surround the extends keyword with square brackets.

type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
 
// 'StrArrOrNumArr' is no longer a union.
type StrArrOrNumArr = ToArrayNonDist<string | number>;

type StrArrOrNumArr = (string | number)[]

4.6 Mapped Types

When you don't want to repeat yourself, sometimes one type needs to be based on another type.

The mapping type is based on the index Signature Syntax. The index signature is used to declare attribute types that have not been declared in advance:

type OnlyBoolsAndHorses = {
  [key: string]: boolean | Horse;
};
 
const conforms: OnlyBoolsAndHorses = {
  del: true,
  rodney: false,
};

A mapping type is a generic type that uses the union of PropertyKeys (usually created through keyof) to iterate through keys to create types:

type OptionsFlags<Type> = {
  [Property in keyof Type]: boolean;
};

In this example, OptionsFlags takes all attributes from type and changes their values to Boolean values.

type FeatureFlags = {
  darkMode: () => void;
  newUserProfile: () => void;
};
 
type FeatureOptions = OptionsFlags<FeatureFlags>;

type FeatureOptions = {
darkMode: boolean;
newUserProfile: boolean;
}

Mapping modifier

There are two additional modifiers that can be applied during the mapping process: readonly and? They affect variability and selectivity, respectively.
You can delete or add these modifiers by adding a prefix - or + to them. If no prefix is added, then + is assumed.

// Removes 'readonly' attributes from a type's properties
type CreateMutable<Type> = {
  -readonly [Property in keyof Type]: Type[Property];
};
 
type LockedAccount = {
  readonly id: string;
  readonly name: string;
};
 
type UnlockedAccount = CreateMutable<LockedAccount>;

type UnlockedAccount = {
id: string;
name: string;
}

// Removes 'optional' attributes from a type's properties
type Concrete<Type> = {
  [Property in keyof Type]-?: Type[Property];
};
 
type MaybeUser = {
  id: string;
  name?: string;
  age?: number;
};
 
type User = Concrete<MaybeUser>;

type User = {
id: string;
name: string;
age: number;
}

Remap keys via as

In TypeScript 4.1 and later, you can use the as clause in the mapping type to remap the keys in the mapping type:

type MappedTypeWithNewProperties<Type> = {
    [Properties in keyof Type as NewKeyType]: Type[Properties]
}

You can create a new attribute name from the previous attribute name by using features such as template text type:

type Getters<Type> = {
    [Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
};
 
interface Person {
    name: string;
    age: number;
    location: string;
}
 
type LazyPerson = Getters<Person>;

type LazyPerson = {
getName: () => string;
getAge: () => number;
getLocation: () => string;
}
You can filter out keys by generating never by condition type:

// Remove the 'kind' property
type RemoveKindField<Type> = {
    [Property in keyof Type as Exclude<Property, "kind">]: Type[Property]
};
 
interface Circle {
    kind: "circle";
    radius: number;
}
 
type KindlessCircle = RemoveKindField<Circle>;

type KindlessCircle = {
radius: number;
}

You can map any union, not just the union of string | number | symbol, but any type of Union:

type EventConfig<Events extends { kind: string }> = {
    [E in Events as E["kind"]]: (event: E) => void;
}
 
type SquareEvent = { kind: "square", x: number, y: number };
type CircleEvent = { kind: "circle", radius: number };
 
type Config = EventConfig<SquareEvent | CircleEvent>

type Config = {
square: (event: SquareEvent) => void;
circle: (event: CircleEvent) => void;
}

4.7 template text type

The template text type is based on the string text type and can be extended into many strings through union.

They have the same syntax as template literal strings in JavaScript, but are used for type positions. When used with a specific text type, the template text generates a new string text type by connecting the content.
For example:

type World = "world";
 
type Greeting = `hello ${World}`;

type Greeting = "hello world"

When unions are used at interpolation locations, the type is a collection of each possible string literal that can be represented by each union member:

type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
 
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;

type AllLocaleIDs = "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"

For each interpolation position in the template text, the union is cross multiplied:

type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
type Lang = "en" | "ja" | "pt";
 
type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`;

type LocaleMessageIDs = "en_welcome_email_id" | "en_email_heading_id" | "en_footer_title_id" | "en_footer_sendoff_id" | "ja_welcome_email_id" | "ja_email_heading_id" | "ja_footer_title_id" | "ja_footer_sendoff_id" | "pt_welcome_email_id" | "pt_email_heading_id" | "pt_footer_title_id" | "pt_footer_sendoff_id"

We usually recommend that people use early generation for large string combinations, but this is useful in smaller cases.

String Union in type

When a new string is defined based on the information in the type, the power of template text comes.
Consider a case where a function (makeWatchedObject) adds a new function called on() to the passed object. In JavaScript, its call may be like: make watchedobject (baseobject). We can think of the basic object as follows:

const passedObject = {
  firstName: "Saoirse",
  lastName: "Ronan",
  age: 26,
};

The on function to be added to the base object requires two parameters, an eventName (string) and a callback (function).

The format of eventName should be attributeinthepasedobject + "changed"; Therefore, firstNameChanged derives from the property firstName in the base object.

When the callback function is called:

  • A type value associated with the attributeInThePassedObject name should be passed; Therefore, since the type of firstName is string, the callback of the firstNameChanged event expects to pass a string to it when called. Similarly, age-related events should be called with a numeric parameter
  • Should have a void return type (to simplify the demonstration)

The naive function signature of on() may be: on(eventName: string, callback: (newvalue: any) = > void). However, in the previous description, we identified important type constraints that we want to record in the code. The template text type lets us bring these constraints into the code.

const person = makeWatchedObject({
  firstName: "Saoirse",
  lastName: "Ronan",
  age: 26,
});
 
// makeWatchedObject has added `on` to the anonymous Object
 
person.on("firstNameChanged", (newValue) => {
  console.log(`firstName was changed to ${newValue}!`);
});

Note that on listens for the event "firstNameChanged", not just "firstName". If we want to ensure that the collection of qualified event names is constrained by the union of attribute names in the monitored object and add "Changed" at the end, our simple specification of on() will become more robust. Although it is very comfortable for us to do such calculations in JavaScript, such as object keys(passedObject). Map (x = > ${x} Changed), template text in the type system provides a similar string operation method:

type PropEventSource<Type> = {
    on(eventName: `${string & keyof Type}Changed`, callback: (newValue: any) => void): void;
};
 
/// Create a "watched object" with an 'on' method
/// so that you can watch for changes to properties.
declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>;

With this, we can build something that goes wrong when given an error attribute:

const person = makeWatchedObject({
  firstName: "Saoirse",
  lastName: "Ronan",
  age: 26
});
 
person.on("firstNameChanged", () => {});
 
// Prevent easy human error (using the key instead of the event name)
person.on("firstName", () => {});

Argument of type '"firstName"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.

// It's typo-resistant
person.on("frstNameChanged", () => {});

Argument of type '"frstNameChanged"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.

5. Other predefined types

5.1 utility type

5.1.1 Partial

Make all attributes in T optional. Construct an optional Type with all properties set to Type. This utility returns a Type that represents all subsets of a given Type.

type Partial<T> = {
    [P in keyof T]?: T[P];
};

For example:

interface Todo {
  title: string;
  description: string;
}
 
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return { ...todo, ...fieldsToUpdate };
}
 
const todo1 = {
  title: "organize desk",
  description: "clear clutter",
};
 
const todo2 = updateTodo(todo1, {
  description: "throw out trash",
});

5.1.2 Required

Make all attributes in T necessary. Type constructs a type consisting of all attributes of set to required. Opposite of Partial.

type Required<T> = {
    [P in keyof T]-?: T[P];
};

For example:

interface Props {
  a?: number;
  b?: string;
}
 
const obj: Props = { a: 5 };
 
const obj2: Required<Props> = { a: 5 };

Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.

5.1.3 Readonly

Make all attributes in T read-only

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

For example:

interface Todo {
  title: string;
}
 
const todo: Readonly<Todo> = {
  title: "Delete inactive users",
};
 
todo.title = "Hello";

Cannot assign to 'title' because it is a read-only property.

This utility can be used to represent an assignment expression that will fail at run time (that is, when trying to reassign the properties of a frozen object).
Object.freeze:

function freeze<Type>(obj: Type): Readonly<Type>;

5.1.4 Record<K extends keyof any, T>

Construct a Type with a set of attributes K of Type T. Construct an object Type with the attribute key Keys and the attribute value Type. This utility can be used to map properties of one Type to another.

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

For example:

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

const cats: Record<CatName, CatInfo>

5.1.5 Pick<T, K extends keyof T>

Select a set of attributes from T whose Keys are in union K. Construct a Type with all properties set to Type readonly, which means that the properties of the constructed Type cannot be reassigned. The Type type is constructed by selecting a set of attributes Keys (string text or union of string text) from it.

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

example:

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}
 
type TodoPreview = Pick<Todo, "title" | "completed">;
 
const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
};
 
todo;

const todo: TodoPreview

5.1.6 Omit<Type, Keys>

Type constructs a type by selecting all attributes from it and then deleting Keys (string text or union of string text).

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

example:

interface Todo {
  title: string;
  description: string;
  completed: boolean;
  createdAt: number;
}
 
type TodoPreview = Omit<Todo, "description">;
 
const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
  createdAt: 1615544252770,
};

todo;

const todo: TodoPreview

type TodoInfo = Omit<Todo, "completed" | "createdAt">;
 
const todoInfo: TodoInfo = {
  title: "Pick up kids",
  description: "Kindergarten closes at 5pm",
};
 
todoInfo;

const todoInfo: TodoInfo

5.1.7 Exclude<UnionType, ExcludedMembers>

UnionType constructs a type ExcludedMembers by excluding all union members that can be assigned to.

  • Exclude those types that can be assigned to U from T
type Exclude<T, U> = T extends U ? never : T;
  • Extract the types that can be assigned to U from T
type Extract<T, U> = T extends U ? T : never;

For example:

type T0 = Exclude<"a" | "b" | "c", "a">;

type T0 = "b" | "c"

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

type T1 = "c"

type T2 = Exclude<string | number | (() => void), Function>;

type T2 = string | number

5.1.8 Extract<Type, Union>

Type constructs a Union type by extracting from all Union members that can be assigned to.
For example:

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

type T0 = "a"

type T1 = Extract<string | number | (() => void), Function>;

type T1 = () => void

5.1.9 NonNullable

type NonNullable<T> = T extends null | undefined ? never : T;

5.1.10 Parameters<T extends (...args: any) => any>

Gets the parameter of the function Type in the tuple. Construct a tuple Type from the Type used in the parameters of the function Type.

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

For example:

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

type T6 = Parameters<string>;

Type 'string' does not satisfy the constraint '(...args: any) => any'.
type T6 = never

type T7 = Parameters<Function>;

Type 'Function' does not satisfy the constraint '(...args: any) => any'. Type 'Function' provides no match for the signature '(...args: any): any'.
type T7 = never

5.1.11 ConstructorParameters<Type extends abstract new (...args: any) => any>

Gets the parameters of the constructor Type in the tuple. Construct a tuple or array Type from the Type of the constructor Type. It produces a tuple Type containing all parameter types (or never if the Type is not the Type of the function).

type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;

example:

type T0 = ConstructorParameters<ErrorConstructor>;

type T0 = [message?: string]

type T1 = ConstructorParameters<FunctionConstructor>;

type T1 = string[]

type T2 = ConstructorParameters<RegExpConstructor>;

type T2 = [pattern: string | RegExp, flags?: string]

type T3 = ConstructorParameters<any>;

type T3 = unknown[]

type T4 = ConstructorParameters<Function>;

Type 'Function' does not satisfy the constraint 'abstract new (...args: any) => any'. Type 'Function' provides no match for the signature 'new (...args: any): any'.

type T4 = never

5.1.12 ReturnType<T extends (...args: any) => any>

Gets the return Type of the function Type. Construct a Type consisting of the return Type of function.

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

For example:

declare function f1(): { a: number; b: string };
 
type T0 = ReturnType<() => string>;

type T0 = string

type T1 = ReturnType<(s: string) => void>;

type T1 = void

type T2 = ReturnType<<T>() => T>;

type T2 = unknown

type T3 = ReturnType<<T extends U, U extends number[]>() => T>;

type T3 = number[]

type T4 = ReturnType<typeof f1>;

type T4 = {
a: number;
b: string;
}

type T5 = ReturnType<any>;

type T5 = any

type T6 = ReturnType<never>;

type T6 = never

type T7 = ReturnType<string>;

Type 'string' does not satisfy the constraint '(...args: any) => any'.
type T7 = any

type T8 = ReturnType<Function>;

Type 'Function' does not satisfy the constraint '(...args: any) => any'. Type 'Function' provides no match for the signature '(...args: any): any'.
type T8 = any

5.1.13 InstanceType<T extends abstract new (...args: any) => any>

Gets the return type of the constructor type

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

Construct a Type that consists of the instance Type of the constructor in.

5.1.14 ThisParameterType

Extract the type of this parameter of the function type. If the function type does not have this parameter, it is unknown.

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

5.1.15 OmitThisParameter

Remove the this parameter from the function Type. If Type does not explicitly declare this parameter, the result is only Type. Otherwise, a new function Type without this parameter will be created from Type '. Generics are removed and only the last overload signature is propagated to the new function Type.

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

example:

function toHex(this: Number) {
  return this.toString(16);
}
 
const fiveToHex: OmitThisParameter<typeof toHex> = toHex.bind(5);
 
console.log(fiveToHex());

5.1.16 ThisType

A token of type this in the context. This utility does not return converted types. Instead, it is used as a token of the context this type. Note that noImplicitThis must have this flag enabled to use this utility.

interface ThisType<T> { }

For example:

type ObjectDescriptor<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);

In the above example, the object makeObject in the methodsto parameter has a context type, which includes thistype < D & M >, so the type of this in the method within the object is. Notice how the type of the attribute is the source of the type in both the inference target and the method. methods{ x: number, y: number } & { moveBy(dx: number, dy: number): number }methodsthis

ThisType tag interface is just an empty interface declared in lib d.ts. The interface behaves like any empty interface except that it is recognized in the context type of the object text.

5.2 intrinsic string operation type

5.2.1 Uppercase

Converts a string literal type to uppercase

type Uppercase<S extends string> = intrinsic;

example

type Greeting = "Hello, world"
type ShoutyGreeting = Uppercase<Greeting>
        // type ShoutyGreeting = "HELLO, WORLD"
           
type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`
type MainID = ASCIICacheKey<"my_app">
       // Where type mainid = "ID my_app"

5.2.2 Lowercase

Converts a string literal type to lowercase

type Lowercase<S extends string> = intrinsic;

example

type Greeting = "Hello, world"
type QuietGreeting = Lowercase<Greeting>
          // Where type QuietGreeting = "hello, world"
 
type ASCIICacheKey<Str extends string> = `id-${Lowercase<Str>}`
type MainID = ASCIICacheKey<"MY_APP">
       // Where type MainID = "id-my_app"

5.2.3 Capitalize

Converts the first character of a string type to uppercase

type Capitalize<S extends string> = intrinsic;

example:

type LowercaseGreeting = "hello, world";
type Greeting = Capitalize<LowercaseGreeting>;
        // Where type Greeting = "Hello, world"

5.2.4 Uncapitalize

Converts the first character of a string type to lowercase

type Uncapitalize<S extends string> = intrinsic;

example:

type LowercaseGreeting = "hello, world";
type Greeting = Capitalize<LowercaseGreeting>;
        // Where type Greeting = "Hello, world"

Keywords: Javascript Front-end TypeScript

Added by heepofajeep on Mon, 28 Feb 2022 06:19:07 +0200