【TS篇】- TypeScript基础学习

407 阅读11分钟

1、TypeScript 的基础类型

基础类型描述
boolean布尔类型
number数值类型。支持十进制、十六进制(0x)、二进制(0b)、八进制(0o)
string字符串类型。 ${name}用来在模板字符串中嵌入字符串
Array数组。元素类型后加[],如number[];使用数字泛型Array<元素类型>,如Array
Tuple元组。表示一个已知元素数量和类型的数组,各元素的类型不必相同。如let arr: [number, string];
enum枚举。一些有代表语义的数字或字符串的常量集合
any任意类型
void没有任何类型。当一个函数没有返回值时,通常定义其返回类型为void
never永不存在的值的类型。常用于异常会抛出异常或根本不会返回值的函数表达式或函数,是任何类型的子类型
null和undefined默认情况下nullundefined是所有类型的子类型。
object非原始数据类型 。除number、string、boolean、symbol、null、undefined之外的类型

enum 类型

当一个变量有几种可能的取值时,可以将它定义为枚举类型,表示一些有代表语义的数字或字符串的常量集合。

枚举类型分为:数字枚举、字符串枚举、异构枚举

1. 数字枚举

第一项枚举成员值默认初始为0,其余以初始值为基值依次递增。

export enum Direction {
  Up = 1,
  Down,
}
console.log(Direction)

1.png

编译之后:

export var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Down"] = 1] = "Down";
})(Direction || (Direction = {}));
console.log(Direction);

2. 字符串枚举

export enum Direction {
  Up = 'UP',
  Down = 'DOWN',
}
console.log(Direction)

2.png

编译之后:

export var Direction;
(function (Direction) {
    Direction["Up"] = "UP";
    Direction["Down"] = "DOWN";
})(Direction || (Direction = {}));
console.log(Direction);

3. 异构枚举

异构枚举的成员值是数字和字符串的混合。

export enum Direction {
  Up = 1,
  Down = 'DOWN',
}

枚举对象与普通对象的区别之一在于枚举对象的成员为只读属性

  • 数字枚举和字符串枚举的区别?

    字符串枚举具有语义化,数字枚举没有。

  • const enumenum的区别?

  1. enum编译后会通过一个函数生成查找对象,const enum编译之后不会生成查找对象,因此更节省性能(现代的 v8可忽略)。

    export const enum Direction {
      Up = 'UP',
      Down = 'DOWN',
    }
    console.log(Direction.Up)
    ​
    // 编译后
    console.log("UP");
    
  2. 不能访问const enum对象,只能访问const enum对象成员,因此不能将const enum赋值给变量,也不能通过索引访问const enum

    const evar1 = EVar // 会报错
    console.log(EVar[0]); // 会报错
    
  3. const enum枚举成员不支持动态计算。常量枚举成员初始值设定项只能包含文字值和其他计算的枚举值。

    let left = 1;
    export const enum Direction {
      Up = 1,
      Down,
      Left = left * 2,  // 常量枚举成员初始值设定项只能包含文字值和其他计算的枚举值
    }
    

3.png

小结:

一般情况都使用enum而不使用const enum,因为enum更灵活。一般定义字符串枚举,更具有语义化,特别场景可使用数字枚举。

unknown 类型

unknown 是 TypeScript 类型系统的另一种顶级类型。

由于 any 类型无法使用 TypeScript 提供的大量的保护机制,为了解决 any 带来的问题,TypeScript 3.0 引入了 unknown 类型。

  • ts 变量赋值

    let A = B,当B类型为A类型或者B类型为A类型的子级时,才会通过。

    因此,所有类型都可以赋值给 unknownunknown 类型只能被赋值给 any 类型和 unknown 类型本身。

  1. unknown类型使用前必须先使用条件控制流或者类型断言来收窄 unknown 到指定的类型

    const doSomething = (val: unknown) => {
      if (typeof val === 'function') {
        val(); // 条件分析,类型收窄到 Function,调用不报错;
      }
      val.trim();  // 没有断言,报错
      (val as string).trim();
    }
    

4.png

由于`unknown`类型变量在使用前必须指定其类型,因此`unknown` 类型会比 `any` 更加安全。

2. 联合类型和交叉类型中的 unknown

联合类型取最大集合,任何类型和 `unknown` 类型的联合类型都会得到 `unknown`

```
type U1 = unknown | null; // unknown
type U2 = unknown | undefined; // unknown
type U3 = unknown | number; // unknown
type U4 = unknown | boolean; // unknown
type U5 = unknown | string[]; // unknown
type U6 = unknown | any; // any
```

在交叉类型中,取最小集合

```
type U7 = unknown & null; // null;
type U8 = unknown & undefined; // undefined;
type U9 = unknown & number; // number;
type U10 = unknown & boolean; // boolean;
type U11 = unknown & string[]; // string[]
type U12 = unknown & any; // any;
```

小结:

  • 尽量用unknown代替any;
  • unknown类型应用场景:使用 未知的 或者 不稳定的 数据来源的数据

Tuple 类型(元组)

已知元素数量和类型的数组,各元素的类型不必相同,元组中每个属性都有一个关联的类型。

例子:

let tupleType: [string, boolean];
tupleType = ["Semlinker", true];

never 类型

never 类型表示的是那些永不存在的值的类型。never类型是任何类型的子类型,因此可以赋值给任何类型。

never 类型应用在无法达到终点的函数。例如:抛出异常的函数

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
  throw new Error(message);
}
​
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
    while (true) {
    }
}

在 TypeScript 中,可以利用 never 类型的特性来实现全面性检查,具体示例如下:

type Foo = string | number;
​
function controlFlowAnalysisWithNever(foo: Foo) {
  if (typeof foo === "string") {
    // 这里 foo 被收窄为 string 类型
  } else if (typeof foo === "number") {
    // 这里 foo 被收窄为 number 类型
  } else {
    // foo 在这里是 never
    const check: never = foo;
  }
}

在 else 分支里面,把收窄为 never 的 foo 赋值给一个显示声明的 never 变量。如果一切逻辑正确,那么这里应该能够编译通过。但是假如后来有一天改了 Foo 的类型:

type Foo = string | number | boolean;

然而忘记同时修改 controlFlowAnalysisWithNever 方法中的控制流程,这时候 else 分支的 foo 类型会被收窄为 boolean 类型,导致无法赋值给 never 类型,这时就会产生一个编译错误。通过这个方式,我们可以确保controlFlowAnalysisWithNever 方法总是穷尽了 Foo 的所有可能类型。也就是使用never,当Foo新增类型会提醒controlFlowAnalysisWithNever方法有误。

通过这个示例,我们可以得出一个结论:使用 never 可以检测出现新增了联合类型,但没有对应的实现的情况,目的就是写出类型绝对安全的代码。

object 类型和 Object 类型

object 类型

object表示非原始类型,也就是除numberstringbooleansymbolnullundefined之外的类型。

例如:

declare function create(o: object | null): void;
​
create({ prop: 0 }); // OK
create(null); // OKcreate(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error

注:使用这种类型,我们不能访问值的任何属性。

示例:

const obj: object = {a: 1};
console.log(obj);  // {a: 1}
console.log(obj.a);  // error: 类型“object”上不存在属性“a”。

Object 类型

TypeScript 定义了另一个与新的 object 类型几乎同名的类型,那就是 Object 类型。该类型是所有 Object 类的实例的类型。

Object由以下两个接口来定义:

  • Object 接口定义了 Object.prototype 原型对象上的属性;
  • ObjectConstructor 接口定义了 Object 类的属性。

1、Object 接口定义

// node_modules/typescript/lib/lib.es5.d.ts
​
interface Object {
  constructor: Function;
  toString(): string;
  toLocaleString(): string;
  valueOf(): Object;
  hasOwnProperty(v: PropertyKey): boolean;
  isPrototypeOf(v: Object): boolean;
  propertyIsEnumerable(v: PropertyKey): boolean;
}

2、ObjectConstructor 接口定义

// node_modules/typescript/lib/lib.es5.d.tsinterface ObjectConstructor {
  /** Invocation via `new` */
  new(value?: any): Object;
  /** Invocation via function calls */
  (value?: any): any;
​
  readonly prototype: Object;
​
  getPrototypeOf(o: any): any;
​
  // ···
}
​
declare var Object: ObjectConstructor;

Object 类的所有实例都继承了 Object 接口中的所有属性。

示例:

6.png

Object类型也不能访问除Object 接口之外的属性。

示例:

const obj2: Object = {a: 1};
console.log(obj2.a);  // error: 类型“object”上不存在属性“a”

Object vs object

  • 类型 Object 包括原始值,而object不包括:

    function func1(x: Object) { }
    func1('semlinker'); // OK
    func1(20); // OK
    ​
    function func2(x: object) { }
    func2('semlinker'); // error
    func2(20); // error
    

    为什么?Object.prototype 的属性也可以通过原始值访问:

    > 'semlinker'.hasOwnProperty === Object.prototype.hasOwnProperty
    true
    

小结:

  • Object 包括原始值,可以访问Object 接口中的所有属性;而object不可以
  • object和Object类型都不能访问自己定义的属性值,需慎用。

2、类型别名简介(Type)

类型别名(type aliase),顾名思义,就是给类型起个别名。类型别名用 type 关键字来定义,有了类型别名,我们书写 TS 的时候可以更加方便简洁。

示例:

type Name = string                              // 基本类型type size = 'big' | 'small';                    // 字面量类型type arrItem = number | string                  // 联合类型const arr: arrItem[] = [1,'2', 3]
​
type Person = {                                 // 对象类型
   name: Name;
   grade: number;
}
​
type Student = Person & { grade: number  }       // 交叉类型type Teacher = Person & { major: string  } 
​
type StudentAndTeacherList = [Student, Teacher]  // 元组类型const list:StudentAndTeacherList = [
  { name: 'lin', grade: 100 }, 
  { name: 'liu', major: 'Chinese' }
]

typeinterface

相同点:

  • 都可以定义一个对象或函数

  • 都允许拓展(extends)

    interface 使用 extends 实现继承, type 使用交叉类型实现继承。interface 可以 extends type, type 也可以 与 interface 类型 交叉 。

    interface extends interface

    interface Name { 
      name: string; 
    }
    interface User extends Name { 
      age: number; 
    }
    

    type 与 type 交叉

    type Name = { 
      name: string; 
    }
    type User = Name & { age: number  };
    

    interface extends type

    type Name = { 
      name: string; 
    }
    interface User extends Name { 
      age: number; 
    }
    

    type 与 interface 交叉

    interface Name { 
      name: string; 
    }
    type User = Name & { 
      age: number; 
    }
    

不同点:

  • interface(接口) 是 TS 设计出来用于定义对象类型的,是对对象的形状进行描述。

  • type 是类型别名,用于给各种类型定义别名,让 TS 写起来更简洁、清晰。

  • type 可以声明基本类型、联合类型、交叉类型、元组,interface 不可以

  • type可以和映射类型一起使用,interface不可以

    type定义的对象类型,属性key可以为type类型和enum类型,

    interface定义的对象类型,属性不key可以为type类型和enum类型,

    示例:

    type TSex = 'man' | 'women';
    enum ESex {Man, Woman}
    ​
    type DaysObj = {
      [key in TSex]: string;
    };
    type DaysObj = {
      [key in ESex]: string;
    };
    ​
    interface DaysObj {
      [key in TSex]: string;  // error:映射的类型可能不声明属性或方法
    };
    interface DaysObj {
      [key in ESex]: string;  // error:映射的类型可能不声明属性或方法
    };
    

typeinterface是完全不同的概念。

interface 是接口,用于描述一个对象。

type 是类型别名,用于给各种类型定义别名,让 TS 写起来更简洁、清晰。

3、TypeScript 的类型断言和类型守卫

3.1 类型断言

TypeScript 类型断言用来告诉编译器已经指定了的确切的类型,它不应该再发出错误。类型断言一般作用于any类型、unknown类型、联合类型的变量

类型断言不属于类型转换。是因为转换通常意味着某种运行时的支持。但是,类型断言纯粹是一个编译时语法,同时,它也是一种为编译器提供关于如何分析代码的方法。

as 语法或“尖括号” 语法
function getLength(arg: number | string): number {
    const str = arg as string
    // const str = <string>arg
    if (str.length) {
        return str.length
    } else {
        const number = arg as number
        return number.toString().length
    }
}

非空断言!

在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null 和非undefined 类型。具体而言,x! 将从 x 值域中排除 null 和 undefined 。

示例:

function sayHello(name: string | undefined) {
  // let sname: string = name; // Error
  let sname: string = name!;
}

3.2 类型守卫

类型守卫可以保证一个字符串是一个字符串,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。目前主要有四种的方式来实现类型保护。

3.2.1 in 关键字
interface Admin {
  name: string;
  privileges: string[];
}
​
interface Employee {
  name: string;
  startDate: Date;
}
​
type UnknownEmployee = Employee | Admin;
​
function printEmployeeInformation(emp: UnknownEmployee) {
  console.log("Name: " + emp.name);
  if ("privileges" in emp) {
    console.log("Privileges: " + emp.privileges);
  }
  if ("startDate" in emp) {
    console.log("Start Date: " + emp.startDate);
  }
}
3.2.2 typeof 关键字
function padLeft(value: string, padding: string | number) {
  if (typeof padding === "number") {
      return Array(padding + 1).join(" ") + value;
  }
  if (typeof padding === "string") {
      return padding + value;
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}

typeof 类型保护只支持两种形式:typeof v === "typename"typeof v !== typename"typename" 必须是 "number""string""boolean""symbol"

3.2.3 instanceof 关键字
interface Padder {
  getPaddingString(): string;
}
​
class SpaceRepeatingPadder implements Padder {
  constructor(private numSpaces: number) {}
  getPaddingString() {
    return Array(this.numSpaces + 1).join(" ");
  }
}
​
class StringPadder implements Padder {
  constructor(private value: string) {}
  getPaddingString() {
    return this.value;
  }
}
​
let padder: Padder = new SpaceRepeatingPadder(6);
​
if (padder instanceof SpaceRepeatingPadder) {
  // padder的类型收窄为 'SpaceRepeatingPadder'
}

4、交叉类型和联合类型

4.1 交叉类型

TypeScript 交叉类型是将多个类型合并为一个新的类型,新类型包含了所有类型的特性。

示例:

interface IPerson {
  id: string;
  age: number;
}
​
interface IWorker {
  companyId: string;
}
​
type IStaff = IPerson & IWorker;
​
const staff: IStaff = {
  id: 'E1006',
  age: 33,
  companyId: 'EFT'
};
​
console.dir(staff)

要点:

type定义交叉类型的时候,不管类型是interface还是type,如果它们具有相同的属性名,则该属性的类型也必须相同。

type C = A & B;  // 如果AB具有相同属性name,则name的类型也必须相同

4.2 联合类型

联合类型表示一个值可以是几种类型之一。 我们用竖线(|)分隔每个类型。

联合类型通常与 nullundefined 一起使用:

let id: number | string;
const sayHello = (name: string | undefined) => {
  /* ... */
};

5、泛型简介

指在在定义函数、接口或者类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

泛型的语法是 <> 里写类型参数,一般可以用 T 来表示。

常见泛型变量代表的意思:

  • T(Type):表示一个 TypeScript 类型
  • K(Key):表示对象中的键类型
  • V(Value):表示对象中的值类型
  • E(Element):表示元素类型

5.1 泛型基本使用

5.1.1 处理函数参数
function print<T>(arg:T):T {
    return arg
}

泛型中的 T 就像一个占位符、或者说一个变量,在使用的时候可以把定义的类型像参数一样传入,它可以原封不动地输出

5.1.2 默认参数
interface Iprint<T = number> {
    (arg: T): T
}function print<T>(arg:T) {
    return arg
}
​
const myPrint: Iprint = print
5.1.3 处理多个函数参数

现在有这么一个函数,传入一个只有两项的元组,交换元组的第 0 项和第 1 项,返回这个元组。

function swap(tuple) {
    return [tuple[1], tuple[0]]
}

用泛型来改造一下:用 T 代表第 0 项的类型,用 U 代表第 1 项的类型。

function swap<T, U>(tuple: [T, U]): [U, T]{
    return [tuple[1], tuple[0]]
}
const res = swap(['lin', 20]);  // [20, 'lin']
5.1.5 定义接口
// 接口请求类型
export type RespCommon<T> = {
  code: string;
  data: T;
  message: string;
  messageArgs: Array<any>;
};

5.2 约束泛型

可以和 interface 结合来约束类型。

interface ILength {
    length: number
}
​
function printLength<T extends ILength>(arg: T): T {
    console.log(arg.length)
    return arg
}

这其中的关键就是 <T extends ILength>,让这个泛型继承接口 ILength,这样就能约束泛型。我们定义的变量一定要有 length 属性,可以通过 TS 编译。

5.3 泛型的一些应用

5.3.1 泛型约束类

定义一个栈,有入栈和出栈两个方法,如果想入栈和出栈的元素类型统一,就可以这么写:

class Stack<T> {
    private data: T[] = []
    push(item:T) {
        return this.data.push(item)
    }
    pop():T | undefined {
        return this.data.pop()
    }
}

在定义实例的时候写类型,比如,入栈和出栈都要是 number 类型,就这么写:

const s1 = new Stack<number>()
s1.push(10);

这是非常灵活的,如果需求变了,入栈和出栈都要是 string 类型,在定义实例的时候改一下就好了:

const s1 = new Stack<string>()
s1.push('aa')

特别注意的是,泛型无法约束类的静态成员

5.3.2 泛型约束类型

使用泛型,可以动态定义type, interface 类型。

// 接口请求类型
export type RespCommon<T> = {
  code: string;
  data: T;
  message: string;
  messageArgs: Array<any>;
};
5.3.3 泛型定义数组
const arr: Array<number> = [1,2,3]
5.3.4 小结
  1. 泛型是指在定义函数、接口或类的时候,不预先指定具体类型,而是在使用的时候再指定类型。
  2. 泛型中的 T 就像一个占位符、或者说一个变量,在使用的时候可以把定义的类型像参数一样传入,它可以原封不动地输出
  3. 泛型在成员之间提供有意义的约束,这些成员可以是:函数参数、函数返回值、类的实例成员、类的方法等。

用一张图来总结一下泛型的好处:

7.jpg

6、TypeScript 中常用的操作符(typeof,keyof, extends)

6.1 typeof

typeof 操作符用于获取变量的类型,返回的是一个类型。

let man = {
  name: 'Tom',
  age: 30,
}
​
type Man = typeof man; // type Man = { name: string; age: number; }

6.2 keyof

keyof 操作符用于获取某种类型的所有键,返回键组成的联合类型。

示例1:

type Point = { x: number; y: number };
type P = keyof Point;
//   ^?type P = keyof Point;
​
const p1: P = 'x'  // 有提示'x' | 'y'

8.png

9.png 示例2:

type Test = keyof {[key: string]: string; };  // string | number  ?????
​
// 因为JavaScript对象键总是强制为字符串,因此obj[0]始终与obj["0"]相同。
type Obj = {
  [key: string]: string;  // keystring类型,其实key可以取number和string
}
const obj1: Obj = {1: '2'};  // ok

10.png

示例3:

type T1 = {
    [key: number]: string;
}
type Test = keyof T1;  // number

11.png

示例4:

string索引签名,获取不到自定义属性名。

type Test = keyof {name: string; [key: string]: string; };  // string | number

name属性丢失???

官方介绍:如果该类型具有stringnumber索引签名,keyof则将返回这些类型。

示例5:

number索引签名,可以获取到自定义的属性名。

type Test = keyof {name: string; [key: number]: string; };  // number | 'name'

12.png

详情见typeof 官网介绍

6.3 T[K]

T[K],表示接口 T 的属性 K 所代表的类型,

interface IPerson {
  name: string;
  age: number;
}
​
let type1:  IPerson['name'] // string
let type2:  IPerson['age']  // number

6.4 extends

不同场景下代表的含义不一样:

  • 表示继承/拓展的含义

    class Animal {
        name: string;
        constructor(theName: string) { this.name = theName; }
        move(distanceInMeters: number = 0) {
            console.log(`${this.name} moved ${distanceInMeters}m.`);
        }
    }
    ​
    class Snake extends Animal {
        constructor(name: string) { super(name); }
        move(distanceInMeters = 5) {
            console.log("Slithering...");
            super.move(distanceInMeters);
        }
    }
    ​
    class Horse extends Animal {
        constructor(name: string) { super(name); }
        move(distanceInMeters = 45) {
            console.log("Galloping...");
            super.move(distanceInMeters);
        }
    }
    ​
    let sam = new Snake("Sammy the Python");
    let tom: Animal = new Horse("Tommy the Palomino");
    ​
    sam.move();
    tom.move(34);
    
  • 表示约束的含义

    场景:在书写泛型的时候,我们往往需要对类型参数作一定的限制,比如希望传入的参数都有 name 属性的数组我们可以这么写:

    function getCnames<T extends { name: string }>(entities: T[]):string[] {
      return entities.map(entity => entity.cname)
    }
    

    这里extends对传入的参数作了一个限制,就是 entities 的每一项可以是一个对象,但是必须含有类型为stringname属性。

  • 表示分配的含义

    extends判断一个类型是不是可以分配给另一个类型,返回断后的类型。

    如果A extends B为true,则类型A可以分配给类型B,类型A具备类型B的一切约束条件。

    type A = {
        id: string;
    }
    type B = {
        id: number;
        age: number;
    }
    type C = {
        id: string;
    }
    type Bool1 = B extends A ? 'yes' : 'no'; // Bool1 => 'no'
    type Bool2 = C extends A ? 'yes' : 'no'; // Bool2 => 'yes'
    

7、TypeScript 中常用的内置工具类型(in,Partial, Required, Readonly, Record, Pick, Exclude, Omit)

7.1 映射类型

旧类型中创建新类型的一种方式 — 映射类型。在映射类型里,新类型以相同的形式去转换旧类型里每个属性。

7.1.1 in

in用来对联合类型或者enum类型实现遍历。

例如:

type Keys = 'option1' | 'option2';
type Flags = { [K in Keys]: boolean };
7.1.2 Partial

Partial<T>构造一个类型,其中T的所有属性都设置为可选

例如:

interface IPerson {
    name: string
    age: number
}
​
type IPartial = Partial<IPerson>
​
let p1: IPartial = {}

使用partial对类型进行优化

优化前:

type TMpiRunRawItem = {
  rank?: string;
  function?: string;
  mpi_call_count?: string;
  rank_list?: string;
};
let mpirunData: TMpiRunRawItem = {};

优化后:

type TMpiRunRawItem = {
  rank: string;
  function: string;
  mpi_call_count: string;
  rank_list: string;
};
let mpirunData: Partial<TMpiRunRawItem> = {};

Partial 原理

Partial的实现用到了inkeyof

/**
 * Make all properties in T optional
 */
type Partial<T> = {
    [P in keyof T]?: T[P]
}
  • [P in keyof T]遍历T上的所有属性
  • ?:设置为属性为可选的
  • T[P]设置类型为原来的类型
7.1.3 Required

Required<T> 构造一个类型,其中T的所有属性都设置为必选

例如:

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>'.
7.1.4 Readonly

Readonly<T>构造一个类型,其中T的所有属性都设置为只读

例如:

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.
7.1.5 Pick

Pick<Type, Keys>通过从Type中抽取属性集Keys来构建类型。

例如:

interface IPerson {
  name: string
  age: number
  sex: string
}
​
type IPick = Pick<IPerson, 'name' | 'age'>
​
let p1: IPick = {
  name: 'lin',
  age: 18
}

Pick 原理

/**
 * From T, pick a set of properties whose keys are in the union K
 */
type Pick<T, K extends keyof T> = {
    [P in K]: T[P]
}

Pick映射类型有两个参数:

  • 第一个参数T,表示要抽取的目标对象
  • 第二个参数K,具有一个约束:K一定要来自T所有属性字面量的联合类型
7.1.6 Record

Record<Keys, Type>构造一个属性键为Keys,属性值为Type的对象类型。

示例:

type petsGroup = 'dog' | 'cat' | 'fish';
interface IPetInfo {
    name:string,
    age:number,
}
​
type IPets = Record<petsGroup, IPetInfo>;
​
const animalsInfo:IPets = {
    dog:{
        name:'dogName',
        age:2
    },
    cat:{
        name:'catName',
        age:3
    },
    fish:{
        name:'fishName',
        age:5
    }
}

7.2 条件类型

7.2.1 Exclude

Exclude<T, U> 返回 联合类型 T 中不包含 联合类型 U 的部分。

type Test = Exclude<'a' | 'b' | 'c', 'a' | 'd'>  // Test: 'b' | 'c'
7.2.2 Extract

Extract<T, U>提取联合类型 T 和联合类型 U 的所有交集。

type Test = Extract<'a' | 'b' | 'c', 'a'>  // Test: 'a'
7.2.3 Omit

Omit<T, U>从类型 T 中剔除 U 中的所有属性。与Pick相反

interface IPerson {
    name: string
    age: number
}
​
type IOmit = Omit<IPerson, 'age'>  // IOmit: {name: string}

8、思考问题:

1、为什么要使用 TypeScript?

JavaScript 是弱类型且动态类型的语言,灵活多变,可以进行隐式转换 ,也可以进行类型检测,但是缺失了类型系统的可靠性。JavaScipt 缺点主要有:

  • 只有在运行阶段才能发现代码的异常,使得隐藏的 bug 难以发现;
  • 隐式类型转换可能产生意想不到的结果;
  • 阅读代码时,难以识别复杂类型的细节。

TypeScript 是 JavaScript 的超集,任何原生的 JavaScript 代码都可以直接通过编译器检查并运行。TypeScript 的静态类型和编译大大降低了发生运行时错误的可能性,同时作为超集,TypeScript 依旧保留了JavaScript 的灵活性

TypeScript 可以让使用 IDE 的开发者通过类型检查及时地发现错误。Bug 越早被发现,就能越早处理。

当然,TypeScript 并不能直接用于 JavaScript 解释引擎,需要编译器将其编译为 JavaScript 代码后才可进行解释,并且添加静态类型检查的 JavaScript 代码会比无类型检查的代码更多更长,类型检查也会花费更多的时间来处理编译时错误。但是,劣质的 JavaScript 代码的开发和维护也很费时,并可能引发严重错误,会消耗更多的时间成本,从总体成本上来说,使用 Typescript 仍然是值得的,对于大项目更是如此

TypeScript 与 JavaScript 的区别

TypeScriptJavaScript
强类型,支持静态和动态类型动态弱类型语言
可以在编译期间发现并纠正错误作为一种解释型语言,只能在运行时发现错误
不允许改变变量的数据类型变量可以被赋予不同类型的值
支持模块、泛型和接口不支持模块,泛型或接口
最终被编译成 JavaScript 代码,使浏览器可以理解可以直接在浏览器中使用
JavaScript 的超集用于解决大型项目的代码复杂性一种脚本语言,用于创建动态网页。

TypeScript的优势

  • 添加了类型系统,增加了代码的可读性和可维护性,有利于协作开发;
  • 静态类型和编译双重检查,可以及时发现一些类型或者接口不匹配的错误,从而大大降低了运行时发生错误的可能性
  • 增强了编辑器(IDE) 的功能,包括代码补全、接口提示、跳转到定义、等;

TypeScript的缺点

  • 编写类型、接口等增加代码量,从而增加工作量;
  • 类型检查也会花费更多的时间来处理编译时错误;

2、为什么滥用 any 会使得代码难以维护?

比如定义一个any类型的对象obj,需要获取obj的属性值name后才执行下一步操作,由于不知道obj是否有name属性,如果name为undefined,对后面的操作就会产生影响。

3、后端接口数据有必要定义类型吗?

有必要。

因为我们用的是TS,强类型语言,使用变量时需要对变量定义类型。而前端对后端返回的数据定义的类型取决于后端返回的数据类型。因此,后端接口数据有必要定义类型。

对于从后端请求的数据,定义类型的好处有:

  1. 在 IDE 中编写代码时有提示,可以知道返回的数据类型,减少书写错误

  2. 数据在组件传递时,易跟踪

    当数据在组件中传递的时候,结构会发生变化。但是我们可以根据初始的数据类型进行转化,而不是重新定义类型。

15.png

type TData = {
    a: number;
    b: number;
    c: number;
}
​
type ChildData1 = Omit<TData, 'a'>;  
// ChildData1 = {
//    b: number;
//    c: number;
// }
​
type ChildData2 = Pick<TData, 'c'>;
// ChildData2 = {
//    c: number;
// }
  1. 当后端的数据结构改变时,易识别

如果对返回的数据不定义类型,隐藏的 bug 不易发现。

比如后端返回一个值a:1,如果a是字符串类型,会影响前端对(a === 1)的判断。