Typescript study notes (below)

1. Type widening

All variables defined by let or var, formal parameters of functions, and non-read-only properties of objects, if they meet the conditions of specifying initial values ​​and not explicitly adding type annotations, then their inferred types are the specified initial value literals The type after type widening is literal type widening.
Let's understand the widening of literal types through the example of string literals:

let str = 'this is string'; // 类型是 string
  let strFun = (str = 'this is string') => str; // 类型是 (str?: string) => string;
  const specifiedStr = 'this is string'; // 类型是 'this is string'
  let str2 = specifiedStr; // 类型是 'string'
  let strFun2 = (str = specifiedStr) => str; // 类型是 (str?: string) => string;
  • Because lines 1 and 2 satisfy the conditions of let and formal parameters without explicitly declaring type annotations, the types of variables and formal parameters are expanded to string (the formal parameter type is exactly string | undefined)
    .
  • Because the constant in line 3 cannot be changed, the type is not widened, so the type of specifiedStr is 'this is string' literal type.
  • In lines 4-5, because the assigned value specifiedStr is a literal type and there is no explicit type annotation, the types of variables and formal parameters are also widened. In fact, such a design meets the actual programming demands. Let's imagine that if the type of str2 is inferred as 'this is string', it will be immutable, because assigning any other value of type string will prompt a type error.

Based on the conditions of literal type widening, we can control the type widening behavior by adding explicit type annotations through the code shown below.

{
    
    
  const specifiedStr: 'this is string' = 'this is string'; // 类型是 '"this is string"'
  let str2 = specifiedStr; // 即便使用 let 定义,类型是 'this is string'
}

In fact, in addition to literal type widening, TypeScript also has a design similar to "Type Widening" (type widening) for certain specific types of values. Let's take a look at it in detail below.
For example, to widen the types of null and undefined, if the variables defined by let and var satisfy the undeclared type annotation and are assigned the value of null or undefined, the type of these variables is inferred to be any:

{
    
    
  let x = null; // 类型拓宽成 any
  let y = undefined; // 类型拓宽成 any

  /** -----分界线------- */
  const z = null; // 类型是 null

  /** -----分界线------- */
  let anyFun = (param = null) => param; // 形参类型是 null
  let z2 = z; // 类型是 null
  let x2 = x; // 类型是 null
  let y2 = y; // 类型是 undefined
}

Note: In strict mode, null and undefined in some older versions (2.0) are not widened to "any".

2. Type narrowing

In TypeScript, we can narrow the type of variables from a relatively broad set to a relatively small and clear set through certain operations, which is "Type Narrowing".
For example, we can use type guards (described later) to narrow the type of function parameters from any to specific types, as follows:

{
    
    
  let func = (anything: any) => {
    
    
    if (typeof anything === 'string') {
    
    
      return anything; // 类型是 string 
    } else if (typeof anything === 'number') {
    
    
      return anything; // 类型是 number
    }
    return null;
  };
}

In VS Code, the prompt type of the anything variable from hover to line 4 is string, and the prompt type to line 6 is number.

Similarly, we can use type guards to narrow down union types to explicit subtypes, as shown in the following example:

{
    
    
  let func = (anything: string | number) => {
    
    
    if (typeof anything === 'string') {
    
    
      return anything; // 类型是 string 
    } else {
    
    
      return anything; // 类型是 number
    }
  };
}

Of course, we can also converge the joint type to a more specific type through literal type equivalent judgment (===) or other control flow statements (including but not limited to if, ternary operator, switch branch), as shown in the following code Show:

{
    
    
  type Goods = 'pen' | 'pencil' |'ruler';
  const getPenCost = (item: 'pen') => 2;
  const getPencilCost = (item: 'pencil') => 4;
  const getRulerCost = (item: 'ruler') => 6;
  const getCost = (item: Goods) =>  {
    
    
    if (item === 'pen') {
    
    
      return getPenCost(item); // item => 'pen'
    } else if (item === 'pencil') {
    
    
      return getPencilCost(item); // item => 'pencil'
    } else {
    
    
      return getRulerCost(item); // item => 'ruler'
    }
  }
}

In the above getCost function, the accepted parameter type is a joint type of literal type, and the function contains 3 process branches of the if statement, and the parameters of the function called by each process branch are specific and independent literal types. Then why can a variable item whose type consists of multiple literals be passed to a function , ,
that only accepts a single specific literal type ? This is because in each flow branch, the compiler knows what type of item is in the flow branch. For example, in the branch of , the item type is shrunk to "pencil". In fact, if we remove the middle process branch from the above example, the compiler can also infer the converged type, as shown in the following code:getPenCostgetPencilCostgetRulerCostitem === 'pencil'

const getCost = (item: Goods) =>  {
    
    
    if (item === 'pen') {
    
    
      item; // item => 'pen'
    } else {
    
    
      item; // => 'pencil' | 'ruler'
    }
  }

3. Type alias

Type aliases are used to give a type a new name. Type aliases are often used with union types.

type Message = string | string[];
let greet = (message: Message) => {
    
    
  // ...
};

Note: Type aliasing, as the name suggests, means we just give the type a new name, not create a new type.

4. Cross type

Intersecting types is combining multiple types into one type. This allows us to superimpose existing types into one type, which contains all the required characteristics of the type, using the &definition intersection type.

{
    
    
  type Useless = string & number;
}

Obviously, if we only merge atomic types such as primitive types, literal types, and function types into cross types, it is useless, because any type cannot satisfy multiple atomic types at the same time, such as both string type and number type. Therefore, in the above code, the type of the type alias Useless is never.
The real use of the cross type is to merge multiple interface types into one type, so as to achieve the effect of equivalent interface inheritance, which is the so-called merged interface type, as shown in the following code:

  type IntersectionType = {
    
     id: number; name: string; } & {
    
     age: number };
  const mixed: IntersectionType = {
    
    
    id: 1,
    name: 'name',
    age: 18
  }

In the above example, we use the intersection type to make the IntersectionType have all the attributes of id, name, and age at the same time. Here we can try to understand the merged interface type as a union set.

think

Here, let's think about it divergently: what will happen if there are attributes with the same name in multiple interface types that are merged?
If the types of attributes with the same name are incompatible, for example, in the above example, the type of the name attribute of the two interface types with the same name is number and the other is string. After merging, the type of the name attribute is the cross type of the two atomic types of number and string, that is never, as shown in the following code:

  type IntersectionTypeConfict = {
    
     id: number; name: string; } 
  & {
    
     age: number; name: number; };
  const mixedConflict: IntersectionTypeConfict = {
    
    
    id: 1,
    name: 2, // ts(2322) 错误,'number' 类型不能赋给 'never' 类型
    age: 2
  };

At this point, if we assign any type of name attribute value to mixedConflict, it will prompt a type error. And if we don't set the name attribute, it will prompt an error that the required name attribute is missing. In this case, it means that the IntersectionTypeConfict type intersected in the above code is a useless type.
If the types of attributes with the same name are compatible, for example, one is number and the other is a subtype of number or a literal type of number, the type of the name attribute after merging is the subtype of the two.
The type of the name attribute in the example shown below is the number literal type 2, so we cannot assign any value other than 2 to the name attribute.

 type IntersectionTypeConfict = {
    
     id: number; name: 2; } 
  & {
    
     age: number; name: number; };

  let mixedConflict: IntersectionTypeConfict = {
    
    
    id: 1,
    name: 2, // ok
    age: 2
  };
  mixedConflict = {
    
    
    id: 1,
    name: 22, // '22' 类型不能赋给 '2' 类型
    age: 2
  };

5. Interface

In TypeScript, we use interfaces (Interfaces) to define types of objects.

5.1 What is an interface

In object-oriented language, interface (Interfaces) is a very important concept. It is an abstraction of behavior, and how to act specifically needs to be implemented by classes (classes).
The interface in TypeScript is a very flexible concept. In addition to being used to [abstract a part of the behavior of the class], it is also often used to describe the "shape of the object (Shape)".

5.2 Simple example

interface Person {
    
    
    name: string;
    age: number;
}
let tom: Person = {
    
    
    name: 'Tom',
    age: 25
};

In the above example, we defined an interface Person, and then defined a variable tom whose type is Person. In this way, we constrain the shape of tom to be consistent with the interface Person.

Interfaces are generally capitalized.

It is not allowed to define variables with fewer attributes than interfaces:

interface Person {
    
    
    name: string;
    age: number;
}
let tom: Person = {
    
    
    name: 'Tom'
};

// index.ts(6,5): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.
//   Property 'age' is missing in type '{ name: string; }'.

More attributes are also not allowed:

interface Person {
    
    
    name: string;
    age: number;
}

let tom: Person = {
    
    
    name: 'Tom',
    age: 25,
    gender: 'male'
};

// index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
//   Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.

It can be seen that when assigning a value, the shape of the variable must be consistent with the shape of the interface

5.3 Optional | Read-Only Properties

interface Person {
    
    
  readonly name: string;
  age?: number;
}

Read-only properties are used to restrict the modification of its value only when the object is just created. In addition, TypeScript also provides ReadonlyArray<T>the type , which Array<T>is similar to except that all variable methods are removed, so it can ensure that the array cannot be modified after it is created.

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!

5.4 Arbitrary attributes

Sometimes we want an interface to allow other arbitrary attributes in addition to mandatory and optional attributes. At this time, we can use the form of index signature to meet the above requirements.

interface Person {
    
    
    name: string;
    age?: number;
    [propName: string]: any;
}

let tom: Person = {
    
    
    name: 'Tom',
    gender: 'male'
};

It should be noted that once any attribute is defined, the type of both the definite attribute and the optional attribute must be a subset of its type

interface Person {
    
    
    name: string;
    age?: number;
    [propName: string]: string;
}

let tom: Person = {
    
    
    name: 'Tom',
    age: 25,
    gender: 'male'
};

// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
//   Index signatures are incompatible.
//     Type 'string | number' is not assignable to type 'string'.
//       Type 'number' is not assignable to type 'string'.

In the above example, the value of any attribute is allowed to be string, but the value of the optional attribute age is number, and number is not a sub-attribute of string, so an error is reported.
In addition, it can be seen from the error message that the type { name: 'Tom', age: 25, gender: 'male' }of { [x: string]: string | number; name: string; age: number; gender: string; }, which is a combination of a joint type and an interface.
Only one arbitrary property can be defined in an interface. If you have properties of more than one type in the interface, you can use union types in any of the properties:

interface Person {
    
    
    name: string;
    age?: number; // 这里真实的类型应该为:number | undefined
    [propName: string]: string | number | undefined;
}

let tom: Person = {
    
    
    name: 'Tom',
    age: 25,
    gender: 'male'
};

5.5 duck type identification method

The so-called duck-type identification method is to walk like a duck and quack, it is called a duck, that is, if it has the characteristics of a duck, it is considered a duck, that is, by formulating rules to determine whether the object implements this interface.
example:

interface LabeledValue {
    
    
  label: string;
}
function printLabel(labeledObj: LabeledValue) {
    
    
  console.log(labeledObj.label);
}
let myObj = {
    
     size: 10, label: "Size 10 Object" };
printLabel(myObj); // OK
interface LabeledValue {
    
    
  label: string;
}
function printLabel(labeledObj: LabeledValue) {
    
    
  console.log(labeledObj.label);
}
printLabel({
    
     size: 10, label: "Size 10 Object" }); // Error

In the above code, writing an object in the parameter is equivalent to directly assigning labeledObja value. This object has a strict type definition, so more or less parameters cannot be used. And when you receive the object with another variable myObj outside, myObj will not be checked for additional attributes, but will be deduced according to the type, and then let myObj: { size: number; label: string } = { size: 10, label: "Size 10 Object" };this myObj will be assigned to labeledObj. At this time, according to the compatibility of the type, the two types Objects, refer to the duck-typing method, because they both have the label attribute, they are considered to be the same as two objects, so this method can be used to bypass redundant type checking.

5.6 Ways to bypass extra attribute checks

5.6.1 Duck dialectics

As shown in the example above

5.6.2 Type assertions

The meaning of type assertion is equivalent to telling the program that you know what you are doing, and the program will naturally not perform additional attribute checks.

interface Props {
    
     
  name: string; 
  age: number; 
  money?: number;
}

let p: Props = {
    
    
  name: "兔神",
  age: 25,
  money: -100000,
  girl: false
} as Props; // OK

5.6.3 Index signature

interface Props {
    
     
  name: string; 
  age: number; 
  money?: number;
  [key: string]: any;
}

let p: Props = {
    
    
  name: "兔神",
  age: 25,
  money: -100000,
  girl: false
}; // OK

6. Generics

6.1 Introduction to Generics

If you were asked to implement a function identitywhose parameter could be any value, and the return value was to return the parameter as it is, and it could only accept one parameter, what would you do?

You will think this is very simple, just write the code like this:

const identity = (arg) => arg;

Since it can accept any value, that is to say, the input parameter and return value of your function should be of any type. Now let's add type declarations to the code:

type idBoolean = (arg: boolean) => boolean;
type idNumber = (arg: number) => number;
type idString = (arg: string) => string;
...

A stupid method is like the above, that is to say, as many types as JS provides, you need to copy as many copies of the code, and then change the type signature. This is fatal for programmers. This kind of copying and pasting increases the probability of errors, makes the code difficult to maintain, and affects the whole body. And if new types are added to JS in the future, you still need to modify the code, which means that your code is open to modification, which is not good. Another way is to use the "universal syntax" of any. What are the disadvantages? Let me give you an example:

identity("string").length; // ok
identity("string").toFixed(2); // ok
identity(null).toString(); // ok
...

If you use any, whatever you write is ok, which loses the effect of type checking. In fact, I know that what I passed to you is a string, and the return must be a string, and there is no toFixed method on the string, so the need to report an error is what I want. In other words, the effect I really want is: when I use id, you can deduce it based on the type I passed to you. For example, if I pass in a string, but use the method on number, you should report an error.

In order to solve the above problems, we use generics to refactor the above code. Different from our definition, a type T is used here, this T is an abstract type, and its value is only determined when it is called, so we don't need to copy and paste countless copies of code.

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

where Tstands for Type and is usually used as the first type variable name when defining generics. But virtually any valid name Tcan be substituted. In Taddition to , the following are the meanings of common generic variables:

  • K (Key): Indicates the key type in the object;
  • V (Value): Indicates the value type in the object;
  • E (Element): Indicates the element type.

insert image description here
In fact, we can not only define one type variable, we can introduce any number of type variables we want to define. For example, we introduce a new type variable U to extend the identity function we defined:

function identity <T, U>(value: T, message: U) : T {
    
    
  console.log(message);
  return value;
}
console.log(identity<Number, string>(68, "Semlinker"));

insert image description here
Instead of explicitly setting values ​​for type variables, it is more common to have the compiler choose these types automatically, resulting in cleaner code. We can omit the angle brackets entirely, like:

function identity <T, U>(value: T, message: U) : T {
    
    
  console.log(message);
  return value;
}
console.log(identity(68, "Semlinker"));

For the above code, the compiler is smart enough to know the types of our parameters and assign them to T and U without the developer needing to specify them explicitly.

6.2 Generic constraints

What if I want to print out the size attribute of the parameter? If TS is not constrained at all, an error will be reported:

function trace<T>(arg: T): T {
    
    
  console.log(arg.size); // Error: Property 'size doesn't exist on type 'T'
  return arg;
}

The reason for the error is that T can be of any type in theory, unlike any, you will report an error no matter what property or method you use (unless this property and method are common to all collections). Then the intuitive idea is to limit the parameter type passed to the trace function to the size type, so that no error will be reported. How to express the point of this type constraint? The key to achieving this requirement is to use type constraints. This can be done using the extends keyword. Simply put, you define a type, and then let T implement this interface.

interface Sizeable {
    
    
  size: number;
}
function trace<T extends Sizeable>(arg: T): T {
    
    
  console.log(arg.size);
  return arg;
}

6.3 Generic utility types

For the convenience of developers, TypeScript has built-in some commonly used tool types, such as Partial, Required, Readonly, Record, and ReturnType. But before the specific introduction, we have to introduce some relevant basic knowledge first, so that readers can better learn other tool types.

1、typeof

The main purpose of typeof is to obtain the type of a variable or property in a type context. Let's understand it through a specific example.

interface Person {
    
    
  name: string;
  age: number;
}
const sem: Person = {
    
     name: "semlinker", age: 30 };
type Sem = typeof sem; // type Sem = Person

In the above code, we get the type of the sem variable through the typeof operator and assign it to the Sem type variable, then we can use the Sem type:

const lolo: Sem = {
    
     name: "lolo", age: 5 }

You can also do the same with nested objects:

const Message = {
    
    
    name: "jimmy",
    age: 18,
    address: {
    
    
      province: '四川',
      city: '成都'   
    }
}
type message = typeof Message;
/*
 type message = {
    name: string;
    age: number;
    address: {
        province: string;
        city: string;
    };
}
*/

In addition, in addition to obtaining the structural type of the object, the typeof operator can also be used to obtain the type of the function object, such as:

function toArray(x: number): Array<number> {
    
    
  return [x];
}
type Func = typeof toArray; // -> (x: number) => number[]

2、keyof

The keyof operator was introduced in TypeScript 2.1. This operator can be used to get all keys of a certain type, and its return type is a union type.

interface Person {
    
    
  name: string;
  age: number;
}

type K1 = keyof Person; // "name" | "age"
type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join" 
type K3 = keyof {
    
     [x: string]: Person };  // string | number

3、in

in is used to traverse enumerated types:

type Keys = "a" | "b" | "c"

type Obj =  {
    
    
  [p in Keys]: any
} // -> { a: any, b: any, c: any }

4、infer

In a conditional type statement, you can use infer to declare a type variable and use it.

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

In the above code, infer R is to declare a variable to carry the return value type of the incoming function signature. Simply put, it is used to obtain the type of the function return value for later use.

5、extends

Sometimes the generics we define do not want to be too flexible or want to inherit certain classes, etc., we can add generic constraints through the extends keyword.

interface Lengthwise {
    
    
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    
    
  console.log(arg.length);
  return arg;
}

Now the generic function is constrained so it no longer applies to any type:

loggingIdentity(3);  // Error, number doesn't have a .length property

At this time, we need to pass in a value that conforms to the constraint type, which must contain the length attribute:

loggingIdentity({
    
    length: 10, value: 3});

7. Introduction to tsconfig.json

tsconfig.json is the configuration file for TypeScript projects. If there is a tsconfig.json file in a directory, it usually means that this directory is the root directory of the TypeScript project.
tsconfig.json contains related configurations for TypeScript compilation. By changing the compilation configuration items, we can make TypeScript compile ES6, ES5, and node codes.

7.1 Important fields of tsconfig.json

1. files - set the name of the file to be compiled;
2. include - set the file to be compiled and support path pattern matching;
3. exclude - set the file that does not need to be compiled and support path pattern matching;
4. compilerOptions - set and Options related to compilation process.

7.2 compilerOptions option

{
    
    
  "compilerOptions": {
    
    
  
    /* 基本选项 */
    "target": "es5",                       // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
    "module": "commonjs",                  // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "lib": [],                             // 指定要包含在编译中的库文件
    "allowJs": true,                       // 允许编译 javascript 文件
    "checkJs": true,                       // 报告 javascript 文件中的错误
    "jsx": "preserve",                     // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
    "declaration": true,                   // 生成相应的 '.d.ts' 文件
    "sourceMap": true,                     // 生成相应的 '.map' 文件
    "outFile": "./",                       // 将输出文件合并为一个文件
    "outDir": "./",                        // 指定输出目录
    "rootDir": "./",                       // 用来控制输出目录结构 --outDir.
    "removeComments": true,                // 删除编译后的所有的注释
    "noEmit": true,                        // 不生成输出文件
    "importHelpers": true,                 // 从 tslib 导入辅助工具函数
    "isolatedModules": true,               // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).

    /* 严格的类型检查选项 */
    "strict": true,                        // 启用所有严格类型检查选项
    "noImplicitAny": true,                 // 在表达式和声明上有隐含的 any类型时报错
    "strictNullChecks": true,              // 启用严格的 null 检查
    "noImplicitThis": true,                // 当 this 表达式值为 any 类型的时候,生成一个错误
    "alwaysStrict": true,                  // 以严格模式检查每个模块,并在每个文件里加入 'use strict'

    /* 额外的检查 */
    "noUnusedLocals": true,                // 有未使用的变量时,抛出错误
    "noUnusedParameters": true,            // 有未使用的参数时,抛出错误
    "noImplicitReturns": true,             // 并不是所有函数里的代码都有返回值时,抛出错误
    "noFallthroughCasesInSwitch": true,    // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)

    /* 模块解析选项 */
    "moduleResolution": "node",            // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
    "baseUrl": "./",                       // 用于解析非相对模块名称的基目录
    "paths": {
    
    },                           // 模块名到基于 baseUrl 的路径映射的列表
    "rootDirs": [],                        // 根文件夹列表,其组合内容表示项目运行时的结构内容
    "typeRoots": [],                       // 包含类型声明的文件列表
    "types": [],                           // 需要包含的类型声明文件名列表
    "allowSyntheticDefaultImports": true,  // 允许从没有设置默认导出的模块中默认导入。

    /* Source Map Options */
    "sourceRoot": "./",                    // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
    "mapRoot": "./",                       // 指定调试器应该找到映射文件而不是生成文件的位置
    "inlineSourceMap": true,               // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
    "inlineSources": true,                 // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性

    /* 其他选项 */
    "experimentalDecorators": true,        // 启用装饰器
    "emitDecoratorMetadata": true          // 为装饰器提供元数据的支持
  }
}

7.3 Some suggestions for writing efficient TS code

7.3.1 Minimize duplication of code

For those who are new to TypeScript, when defining an interface, the following similar repetitive code may accidentally appear. for example:

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

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

Obviously, compared to the Person interface, PersonWithBirthDatethe interface only has one more birth attribute, and other attributes are the same as the Person interface. So how to avoid the duplication of code in the example? To solve this problem, you can take advantage of extendsthe keyword :

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

interface PersonWithBirthDate extends Person {
    
     
  birth: Date;
}

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

type PersonWithBirthDate = Person & {
    
     birth: Date };

Additionally, sometimes you may find yourself wanting to define a type to match the "shape" of an initial configuration object, such as:

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

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

In fact, for the Options interface, you can also use the typeof operator to quickly obtain the "shape" of the configuration object:

type Options = typeof INIT_OPTIONS;

In actual development, duplicate types are not always easy to spot. Sometimes they are masked by syntax. For example, there are multiple functions with the same type signature:

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

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

type HTTPFunction = (url: string, opts: Options) => Promise<Response>; 
const get: HTTPFunction = (url, opts) => {
    
     /* ... */ };
const post: HTTPFunction = (url, opts) => {
    
     /* ... */ };

7.3.2 Using a more precise type instead of a string type

Say you're building a music collection and want to define a genre for albums. At this point you can use the interface keyword to define an Album type:

interface Album {
    
    
  artist: string; // 艺术家
  title: string; // 专辑标题
  releaseDate: string; // 发行日期:YYYY-MM-DD
  recordingType: string; // 录制类型:"live" 或 "studio"
}

For Albumthe type , you want releaseDatethe format of the attribute value toYYYY-MM-DD be live or studio. recordingTypeBut releaseDatebecause recordingTypethe types of the and attributes in the interface are both strings, the following problems may occur when using the Album interface:

const dangerous: Album = {
    
    
  artist: "Michael Jackson",
  title: "Dangerous",
  releaseDate: "November 31, 1991", // 与预期格式不匹配
  recordingType: "Studio", // 与预期格式不匹配
};

releaseDateAlthough recordingTypethe values ​​of and don't match the expected format, the TypeScript compiler doesn't spot the problem at this point. To fix this, you releaseDateshould recordingTypedefine more precise types for the and properties, like this:

interface Album {
    
    \
  artist: string; // 艺术家
  title: string; // 专辑标题
  releaseDate: Date; // 发行日期:YYYY-MM-DD
  recordingType: "studio" | "live"; // 录制类型:"live" 或 "studio"
}

After redefining the Album interface, the TypeScript compiler will prompt the following exception information for the previous assignment statement:

const dangerous: Album = {
    
    
  artist: "Michael Jackson",
  title: "Dangerous",
  // 不能将类型“string”分配给类型“Date”。ts(2322)
  releaseDate: "November 31, 1991", // Error
  // 不能将类型“"Studio"”分配给类型“"studio" | "live"”。ts(2322)\
  recordingType: "Studio", // Error
};

In order to solve the above problem, you need to set the correct type for the releaseDate and recordingType properties, such as this:

const dangerous: Album = {
    
    
  artist: "Michael Jackson",
  title: "Dangerous",
  releaseDate: new Date("1991-11-31"),
  recordingType: "studio",
};

Guess you like

Origin blog.csdn.net/weixin_55608297/article/details/130686124