preface

There are two difficulties with learning TypeScript. One is to understand the syntax of TS. At present, TS has many grammatical features. If you understand the syntax, you will find that some seemingly complicated problems are actually very simple. You just don’t know the corresponding grammatical features to solve the problems. The second is to understand type characteristics. Types such as enum, union, and tuple in TS provide different types of declaration. Some of these types are highly similar to each other, and some can be transformed into each other. A more comprehensive understanding of these types will help us solve some of typescript’s challenges.

TypeScript Basic Types

Tuple and Enum are among TypeScriptBasic Types. Because of its unique features, it is worth learning more about.

  • Boolean

  • Number

  • String

  • Array

  • Tuple

  • Enum

  • Unknown

  • Any

  • Void

  • Null and Undefined

  • Never

  • Object

Tuple

The way to define tuples is simple.

// Declare a tuple type
let x: [string.number];
// Initialize it
x = ["hello".10]; // OK
// Initialize it incorrectly
x = [10."hello"]; // Error

type NestedTuple = [string.Array<number>,boolean, {name: string}]]
// Complex tuples can be defined
Copy the code

Tuple -> Union

Tuple turns into Union a lot of times, but let’s just write it

type Tuple = [string.number.boolean]
type Union = Tuple[number] // string | number | boolean
Copy the code

Utility type is going to be

type Tuple2Union<Tuple extends readonly unknown[]> = Tuple[number]

type Tuple = [string.number.boolean]
type Union = Tuple2Union<Tuple>
Copy the code

On the other hand, Union to Tuple is almost never used, so ignore it.

There are a lot of specific scenarios for transformation, and this technique is usually used during iteration. For example, we want to map an object by the type defined by the tuple.

type Tuple = ["blue"."yellow"."red"]
// Want to convert a structure {blue: string, yellow: string, red: string}
type MappedTuple = {
    [k in Tuple[number]] :string
}
Copy the code

As an digression, always note the object of extends in Type. Know which extends which

type Includes<T extends readonly any[], P> = P extends T[number]?true: false
// correct!
type Includes<T extends readonly any[], P> = T[number] extends P ? true: false
// incorrect!

type isPillarMen = Includes<['Kars'.'Esidisi'.'Wamuu'.'Santana'].'Wamuu'> // expected to be `false`
Copy the code

Enum

Enumerations are a way to provide friendlier names for a set of values.

enum Color {
  Red = 1,
  Green,
  Blue,
}
let c: Color = Color.Green;
Copy the code

The key of an enum must be a string, and its value must be either a string or a number.

1. Enumeration of numbers

enum Color {
  Red = 1,
  Green,
  Blue,
}
Copy the code

2. Enumeration of strings

enum CardinalDirection {
  North = 'N',
  East = 'E',
  South = 'S',
  West = 'W',}Copy the code

3. Mixed enumeration

enum BooleanLikeHeterogeneousEnum {
  No = 0,
  Yes = "YES",}Copy the code

In short, don’t use it.

Property 1 is not only a type, but can also be used as a value

enum Color {
  Red = 1, Green, Blue,} the Color enumeration defined by 👆 above is set to {compile1: "Red".2: "Green".3: "Blue".Blue: 3.Green: 2.Red: 1,}let colorName: string = Color[2];
console.log(colorName); // 'Green'

enum CardinalDirection {
  North = 'N',
  East = 'E',
  South = 'S',
  West = 'W'} the CardinalDirection enumeration defined by 👆 above has a value of {compileEast: "E".North: "N".South: "S".West: "W"} Note that numeric enumeration and string enumeration end values are different.enumCan be used asobjectTo use!Copy the code

Feature 2 Loose type- Checking

Numeric enumerations have loose type-checking problems. For example

const color1: Color = 4 // Ok
const color2: Color.Red = 5 // Ok
const color3: Color ='6' // Error
Copy the code

We expected all three expressions to report errors, but the reality is that only the last one will report type error. The cause of this problem is shown here. Therefore, it is recommended to use string enumerations when using enums, or to avoid writing the following code at all.

enum Color {
  Red = 1,
  Green,
  Blue,
}

const value1: Color = 3
// Don't write that!
const value2: Color = Color.Blue
// Write it correctly

function foo(arg: Color) {
  if (arg === 1) {
    // Don't write that!
  }
  if (arg === Color.Red) {
    // Write it correctly}}Copy the code

The interesting thing is that type-checking is normal when we need to create a ColorMap through the Color enumeration.

const ColorMap: {
 [key in Color]: string;
} = {
  1: 'red color'.2: 'green color'.3: 'blue color'.4: 'x' // Error!
}
Copy the code

Typescript will report an error if the subscript is 4.

const enum 和 declare const

Enumerations can be created as const enums.

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

let directions = [
  Direction.Up,
  Direction.Down,
  Direction.Left,
  Direction.Right,
];
// let directions = [
// 0 /* Up */,
// 1 /* Down */,
// 2 /* Left */,
// 3 /* Right */,
// ];
const value = Direction[Direction.Up] / / an error!
Copy the code

The difference between the compiled version above and the Direction enumeration without const is that the Direction enumeration with const compilation does not exist as a value. Everything that is used as a value is converted to the corresponding enumerated value. Enumerations can no longer be used as objects.

Enumerations defined by declare enum cannot be used as values.

declare enum Direction {
  Up,
  Down,
  Left,
  Right,
}
let directions = [
  Direction.Up,
  Direction.Down,
  Direction.Left,
  Direction.Right,
];
Copy the code

This way, typescript does not explicitly report errors, but typescript compilation fails. In short, don’t use enumerations that way.

Enum -> Union

An Enum contains a key and a value.

EnumKey form a Union

There are often scenarios where you need to get the union of an enum key. Enum The process of converting the key of an object to a Union is the same as that of converting the key of an object to a Union.

enum CardinalDirection {
  North = 'N',
  East = 'E',
  South = 'S',
  West = 'W',}const DirectionObj = {
  North: 'N'.East: 'E'.South: 'S'.West: 'W',}type Type1 = keyof typeof CardinalDirection // "North" | "East" | "South" | "West"
type Type2 = keyof typeof DirectionObj // "North" | "East" | "South" | "West"
Copy the code

The same is true for enumerations, which do not occur during rotation

enum Direction {
  Up,
  Down,
  Left,
  Right,
}
type Type = keyof typeof Direction // "Up" | "Down" | "Left" | "Right"

/ / not be 0 | 1 | 2 | 3 | | "Up" "Down" | "Left" | "Right"
// To see why this type is possible, you can look further up at the difference between numeric and string enumerations
Copy the code

EnumValue form a Union

How to through the following enumeration “N” | “E” | “S” | “W” the Union?

enum CardinalDirection {
  North = 'N',
  East = 'E',
  South = 'S',
  West = 'W',}Copy the code

Methods are also available, using typescript template strings.

type ValueUnion = `${CardinalDirection}`
// "N" | "E" | "S" | "W"
Copy the code

Union

Union is also easy to understand. It’s multiple types of or.

function printId(id: number | string) {
  console.log("Your ID is: " + id);
}
// OK
printId(101);
// OK
printId("202");
// Error
printId({ myID: 22342 });
Copy the code
type UnionType = number| | {}string | '123' | 2312
// number | {} | string
Copy the code

The two types defined below are quite different.

type Union = Array<string | number> // (string | number)[]
type Tuple = [string.number]
Copy the code

Here the length is fixed, 2 and the first element is string and the second element is number. But the Union type does not limit length. And each element can be either a string or a number. The two expressions have different meanings.

Union is used in conditional types

This is usually in the form of XXX extends UnionType, and TS will help us decide whether to include. For example,

type Nullish = null | undefined
type isNullish<T> = T extends Nullish ? true : false

type isNull = isNullish<null> // true
type isNull2 = isNullish<number> // false

// We can have some more fun
type isNull3 = isNullish<null|undefined> // This is still true
Copy the code

Union is used in mappd types

Union is often used in mapped types. First, the following P type “x” | “y”

type Point = { x: number; y: number };
type P = keyof Point; // "x" | "y"
Copy the code
type Union = "x" | "y"
type Obj = {
  [k in Union]: string
} // { x: string; y: string };
Copy the code

Therefore, the following five type maps will be the same

type Union = "x" | "y"
type Point = { x: number; y: number };
type Tuple = ["x"."y"]
enum EnumMapKey {
  x,
  y,
}
enum EnumMapValue {
  First = 'x',
  Second = 'y',}/ / the first
type Obj1 = {
  [k in Union]: string
}
/ / the second
type Obj2 = {
  [k in keyof Point]: string
}
/ / the third kind
type Obj3 = {
  [k in Tuple[number]] :string
}
/ / the fourth
type Obj4 = {
  [k in keyof typeof EnumMapKey]: string
}
/ / 5 kinds
type Obj5 = {
  [k in `${EnumMapValue}`] :string} ortype Obj5 = {
  [k in EnumMapValue]: string
}
// The effect is the same.
Copy the code

Use of Union in Template Literal types

In addition to the Template Literal types used above to combine values from enumerations into unions, it can also be combined with unions to do great things.

type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type ThreeDigits = `${Digit}${Digit}${Digit}`;
001 / / "000" | "" |" 002 "|" 003 "|" 004 "|" 005 "|" 006 "|" 007 "|" 008 "|" 009 "|" 010 "|" 011 "|" 012 "|" 013 "|" 014" 016 "015" | | "" |" 017 "|" 018 "|" 019 "|" 020 "|" 021 "|" 022 "|... 976 more ... | "999"
Copy the code

Now we can implement permutations and combinations of unions. But notice that after the conversion, the type is changed to string instead of number. If you do the following, you get a Tserror.

type UniType = string | number | boolean | [number]
type Template = `${UniType}`

// Type 'UniType' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
Copy the code

Union members must be of simple type.

A small test

Suppose we have a function foo that takes two arguments, but this function is special in that it takes either a string and {name: string} or two arguments of type number. So how to define this function type.

foo('xx', {name: 'hello'}) // correct
foo(3232.232) // correct
foo('xx', 123) // error!
Copy the code

There are two options, and the answers are as follows

function foo(. args: [string, {name: string|}] [number.number]) {
}

foo('xx', {name: 'hello'})
foo(3232.232)
foo('xx'.123) // error!

function bar(arg1: string, arg2: {name: string}) :void
function bar(arg1: number, arg2: number) :void
function bar(arg1: string | number, arg2: {name: string} | number) {}bar('xx', {name: 'hello'}) / /correct
bar(3232.232) / /correct
bar('xx'.123) / /error! // The first is obvious.Copy the code