TypeScript知识点

124 阅读26分钟

禁止转载,侵权必究!

1. 基础类型

TS 中冒号后面的是类型标识, 等号后面是值

常见的类型:基础类型、高级类型 = 自定义类型; ts 包中内置了很多类型

  • 1). ts 类型是从安全的角度出发的,一切从安全角度考虑
  • 2). ts 是在开发的时候来检测 不是在运行的时候, 所以代码并没有被真正的执行
  • 3). ts 中具备一个类型推导的特点, 不是所有的变量都需要增加类型。只有无法推断或者推断错误的时候才需要编写类型
  • 4). ts 最终编译后 类型就消失了(类型就是空气)
  • 5). 真正开发的时候 都是使用模块化开发 export {}; 原始数据类型 都是采用小写的类型, 大写类型(包装类型)用来描述的实例
let s1: string = "abc";
// let s2:String = new String('abc'); // 包装对象 , 在 ts 中 大写类型可以描述实例
// let s3:String = 'abc';

"abc".charAt(); // 默认当我们调用基本类型的方法时 会将当前基本类型包装成对象类型

class Dog {}
let dog: Dog = new Dog();

布尔、数字、字符串类型

let bool: boolean = true;
let num: number = 10;
let str: string = "hello zf";

数组的类型: [], 数组概念:数组是多个相同类型的数据集合; JS 中数组可以随意存放

ts 中有两种方式可以标注数组类型

let arr1: number[] = [1, 2, 3];
let arr2: string[] = ["1", "2", "3"];
let arr3: (number | string)[] = [1, "2", 3]; // 联合类型
let arr4: Array<number | string> = [1, "2", 3]; // 泛型方式来声明

元组(长度固定,类型固定)

let tuple: [string, number, boolean] = ["zf", 10, true];
// 像元组中增加数据,只能增加元组中存放的类型
tuple.push("回龙观");

枚举(自带类型的对象, 枚举的值如果没有赋值 从 0 开始递增; 反举只能在我们值为数字的情况)

enum USER_ROLE { // 代码中的常量可以全部采用枚举类型,提示友好
  USER, // 默认从 0 开始
  ADMIN,
  MANAGER,
}
// {0: "USER", 1: "ADMIN", 2: "MANAGER", USER: 0, ADMIN: 1, MANAGER: 2}
// 编译后的结果
(function (USER_ROLE) {
  USER_ROLE[(USER_ROLE["USER"] = 0)] = "USER";
  USER_ROLE[(USER_ROLE["ADMIN"] = 1)] = "ADMIN";
  USER_ROLE[(USER_ROLE["MANAGER"] = 2)] = "MANAGER";
})(USER_ROLE || (USER_ROLE = {}));

异构枚举

enum USER_ROLE {
  USER = "user",
  ADMIN = 1,
  MANAGER,
}

常量枚举

const enum USER_ROLE {
  USER,
  ADMIN,
  MANAGER,
}
console.log(USER_ROLE.USER); // console.log(0 /* USER */);

常量枚举不能反举 (一般用不到反举, 都采用常量枚举) 不会生成对象, 而是直接将值拿出来了

.any 类型

不进行类型检测, 一但用户 用了 any 之后, 所有的校验都消失了,如果一个变量声明的时候没有赋值默认也是 any

let arr: any = ["jiagou", true, { name: "zf" }];

.null 和 .undefined

默认情况下 null 和 undefined 只能赋予给 null undefined; 如果在非严格 null 检测的情况下 那么 undefined 和 null 是任何类型的子类型

任何类型的子类型,如果 strictNullChecks 的值为 true,则不能把 null 和 undefined 付给其他类型

let name: number | boolean;
name = null;

.void 类型 函数的返回值可以用 void 来标识, 其他情况下用不到;

只能接受 null,undefined。一般用于函数的返回值

严格模式下不能将 null 赋予给 void

let a: void;
a = undefined;

.never 类型

任何类型的子类型,never 代表不会出现的值。不能把其他类型赋值给 never

function error(message: string): never {
  throw new Error("err");
}
function loop(): never {
  // 函数永远无法到达终点
  while (true) {}
}
// 校验逻辑的完整性 可以利用 never 特性 实现完整性保护
function fn(x: number | string) {
  if (typeof x == "number") {
  } else if (typeof x === "string") {
  } else {
    console.log(x); // never
  }
}

.Symbol 类型

Symbol 表示独一无二

const s1 = Symbol("key");
const s2 = Symbol("key");
console.log(s1 == s2); // false

.BigInt 类型

  • bigInt 不能赋予给 number
  • bigint 可以安全地存储和操作大整数, 目前兼容性不是很好
const num1 = Number.MAX_SAFE_INTEGER + 1;
const num2 = Number.MAX_SAFE_INTEGER + 2;
console.log(num1 == num2); // true

let max: bigint = BigInt(Number.MAX_SAFE_INTEGER);
console.log(max + BigInt(1) === max + BigInt(2));

.object 对象类型

表示非原始类型

大写的 Object 类型 不用(万物皆对象 最终都会找到 Object) {} 字面量类型 {} = new Object 一般不会这样使用

let create = (obj: object): void => {};
create({});
create([]);
create(function () {});
// create(123) // 报错

2. 类型推导

2.1. 类型推导

声明变量没有赋予值时默认变量是 any 类型

let name; // 类型为 any
name = "zf";
name = 10;

声明变量赋值时则以赋值类型为准

let name = "zf"; // name 被推导为字符串类型
name = 10;

2.2.包装对象

我们在使用基本数据类型时,调用基本数据类型上的方法,默认会将原始数据类型包装成对象类型

let bool1: boolean = true;
let bool2: boolean = Boolean(1);
let bool3: Boolean = new Boolean(2);

boolean 是基本数据类型 , Boolean 是他的封装类

2.3.联合类型

在使用联合类型时,没有赋值只能访问联合类型中共有的方法和属性 联合类型 并集; 意味着 全部的意思;

let name: string | number; // 联合类型
console.log(name!.toString()); // 公共方法
name = 10;
console.log(name!.toFixed(2)); // number 方法
name = "zf";
console.log(name!.toLowerCase()); // 字符串方法

这里的!表示此值非空; 即非空断言 这个变量一定有值

let ele: HTMLElement | null = document.getElementById("#app");
ele!.style.color = "red"; // 断定 ele 元素一定有值

// ?? 空值合并运算符 只判断 null 和 undefined

let r1 = null ?? "a";
console.log(r1); // 输出: a

let r = 0 ?? "a"; // 0 也是 false 但是可以返回;
console.log(r); // 输出:0;
  • ?.是 js 语法 叫链判断运算符, 取值操作, 这个值没有就不取值了;
  • !.是 ts 语法 意味着这个值存在

2.4.类型断言

类型断言: 自己指定特定的类型

let name: string | number;
(name! as number).toFixed(2); // 强制
(<number>name!).toFixed(2); // 不推荐使用

尽量使用第一种类型断言因为在 react 中第二种方式会被认为是 jsx 语法

双重断言

let name: string | boolean;
name! as any as string;

尽量不要使用双重断言,会破坏原有类型关系,断言为 any 是因为 any 类型可以被赋值给其他类型

2.5.字面量类型

字面量类型和联合类型 放在一起用 就更加灵活了

type Direction = "Up" | "Down" | "Left" | "Right";
let direction: Direction = "Down"; // 字面量类型 就是限定了值, 和枚举类似

3.函数类型

3.1.函数的两种声明方式

通过 function 关键字来进行声明

function sum(a: string, b: string): string {
  return a + b;
}
sum("a", "b");

可以用来限制函数的参数和返回值类型

通过表达式方式声明

type Sum = (a1: string, b1: string) => string;
let sum: Sum = (a: string, b: string) => {
  return a + b;
};

3.2.可选参数

let sum = (a: string, b?: string): string => {
  // ? 表示可选参数
  return a + b;
};
sum("a"); // 可选参数必须在其他参数的最后面

3.3.默认参数

let sum = (a: string, b: string = "b"): string => {
  return a + b;
};
sum("a"); // 默认参数必须在其他参数的最后面

3.4.剩余参数

const sum = (...args: string[]): string => {
  return args.reduce((memo, current) => (memo += current), "");
};
sum("a", "b", "c", "d");

typeof 取变量的类型 返回的是类型; keyof 取的是类型的 key 的集合

function getName(this: Person, key: PersonKey) {
  return this[key];
}
const person = { name: "jw" };
type Person = typeof person; // {name: string}
type PersonKey = keyof Person;

getName.call(person, "name");

3.5.函数的重载

定义: 定义重名的方法, 通过定义不同的输入来区分这些方法

function toArray(value: number): number[];
function toArray(value: string): string[];
function toArray(value: number | string) {
  if (typeof value == "string") {
    return value.split("");
  } else {
    return value
      .toString()
      .split("")
      .map((item) => Number(item)); // map(Number)
  }
}
toArray(123); // 根据传入不同类型的数据 返回不同的结果  [1, 2, 3]
toArray("123"); // ['1', '2', '3']

5.类

类组成部分:构造函数 属性(实例属性,原型属性) 方法(实例方法,原型方法,访问器(get set)) 静态属性和方法 原型属性是共享的 实例属性是每个人 都有的,独一无二的

5.1.TS 中定义类

class Pointer {
  x!: number; // 实例上的属性必须先声明再使用
  y!: number;
  constructor(x: number, y?: number, ...args: number[]) {
    // 默认值,可选参数,剩余运算符
    this.x = x;
    this.y = y as number;
  }
}
let p = new Pointer(100, 200);

实例上的属性需要先声明再使用,构造函数中的参数可以使用可选参数和剩余参数

5.2.类中的修饰符

public 修饰符(谁都可以访问到)

class Animal {
  public name!: string; // 不写 public 默认也是公开的
  public age!: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}
class Cat extends Animal {
  constructor(name: string, age: number) {
    super(name, age);
    console.log(this.name, this.age); // 子类访问
  }
}
let p = new Cat("Tom", 18);
console.log(p.name, p.age); // 外层访问 // Tom 18
class Animal {
  constructor(public name: string, public age: number) {
    this.name = name;
    this.age = age;
  }
}

我们可以通过参数属性来简化父类中的代码

protected 修饰符 (自己和子类可以访问到)

class Animal {
  constructor(protected name: string, protected age: number) {
    this.name = name;
    this.age = age;
  }
}
class Cat extends Animal {
  constructor(name: string, age: number) {
    super(name, age);
    console.log(this.name, this.age);
  }
}
let p = new Cat("Tom", 18);
console.log(p.name, p.age); // 无法访问

private 修饰符 (除了自己都访问不到)

class Animal {
  constructor(private name: string, private age: number) {
    this.name = name;
    this.age = age;
  }
}
class Cat extends Animal {
  constructor(name: string, age: number) {
    super(name, age);
    console.log(this.name, this.age); // 无法访问
  }
}
let p = new Cat("Tom", 18);
console.log(p.name, p.age); // 无法访问

readonly 修饰符 (仅读修饰符)

class Animal {
  constructor(public readonly name: string, public age: number) {
    this.name = name;
    this.age = age;
  }
  changeName(name: string) {
    this.name = name; // 报错,仅读属性只能在 constructor(即初始化)中被赋值
  }
}
class Cat extends Animal {
  constructor(name: string, age: number) {
    super(name, age); // Animal.call(this,name,age) 调用 Animal 里的 name, age
  }
}
let p = new Cat("Tom", 18);
p.changeName("Jerry");

5.3.静态属性和方法

class Animal {
  static type = "哺乳动物"; // 静态属性
  static getName() {
    // 静态方法
    return "动物类";
  }
  private _name: string = "Tom";

  get name() {
    // 属性访问器
    return this._name;
  }
  set name(name: string) {
    this._name = name;
  }
}
let animal = new Animal();
console.log(animal.name);

/* object.defineProperty(Animal.prototype, "name", {
  get: function () {},
  set: function () {},
  enumerable: false,
  configurable: true,
}); */

静态属性和静态方法是可以被子类所继承的

5.4.Super 属性

super 指向父类 或 父类的原型

class Animal {
  public eat: () => void; // 实例方法
  constructor() {
    this.eat = () => {}; //
  }
  say1(): void {} // 不关心返回的值 原型方法
  say(message: string) {
    // 原型方法
    console.log(message);
  }
  static getType() {
    return "动物";
  }
}
class Cat extends Animal {
  say() {
    // 原型方法中的 super 指代的是父类的原型
    super.say("猫猫叫");
  }
  static getType() {
    // 静态方法中的 super 指代的是父类
    return super.getType();
  }
}
let cat = new Cat();
console.log(Cat.getType()); // 动物

// 子类重写父类的方法 要求必须和父类的方法类型一致
  • 正常类中:原型属性(get xxx 属性访问器来实现)、原型方法(say() {})Animal.prototype

  • 实例属性: 实例方法 (声明在实例上)

  • 静态属性: 静态方法 (声明在类上)

  • super 在构造函数中、静态方法中 super 指向的是父类; 在原型方法中 super 指向的是父类的原型

  • 实例属性要提前声明 修饰符 private protected public readonly

// 构造函数中添加了 protected 和 private 意味着不能再 new 了
class Animal3 {
    private constructor(){} // 用于单例设计模式
//protected constructor(){}
}
class Monkey extend Animal3{}
// new Animal3(); // 报错;protected 将父类改为不能 new 的状态

class Singleton{
    static instance = new Singleton();
    private constructor(){}
    static getInstance(){
        return this.instance; // 谁调 this 指向谁
    }
}
let instance1 = Singleton.getInstance(); // 单例模式
let instance2 = Singleton.getInstance(); // 单例模式
console.log(instance1 === instance2) // true

5.5.类的装饰器

装饰类

function addSay(target: any) {
  target.prototype.say = function () {
    console.log("say");
  };
}
@addSay
class Person {
  say!: Function;
}
let person = new Person();
person.say();

装饰类可以给类扩展功能,需要开启 experimentalDecorators:true

装饰类中属性

function toUpperCase(target:any,key:string){
    let value = target[key];
    Object.defineProperty(target,key,{
        get(){
            return value.toUpperCase();
        },
        set(newValue){
            value = newValue
        }
    })
}
function double(target: any, key: string) {
    let value = target[key];
    Object.defineProperty(target, key, {
        get() {
            return value \* 2;
        },
        set(newValue) {
            value = newValue
        }
    })
}
class Person {
    @toUpperCase
    name: string = 'JiangWen'
    @double
    static age: number = 10;
    getName() {
        return this.name;
    }
}
let person = new Person();
console.log(person.getName(),Person.age)

装饰属性可以对属性的内容进行改写,装饰的是实例属性则 target 指向类的原型、装饰的是静态属性则 target 执行类本身~

装饰类中方法

function noEnum(target: any, key: string, descriptor: PropertyDescriptor) {
  console.log(descriptor);
  descriptor.enumerable = false;
}
class Person {
  @toUpperCase
  name: string = "JiangWen";
  @double
  static age: number = 10;
  @noEnum
  getName() {
    return this.name;
  }
}
let person = new Person();
console.log(person); // getName 不可枚举

装饰参数

function addPrefix(target: any, key: string, paramIndex: number) {
  console.log(target, key, paramIndex); // Person.prototype getName 0
}
class Person {
  @toUpperCase
  name: string = "JiangWen";
  @double
  static age: number = 10;
  prefix!: string;
  @noEnum
  getName(@addPrefix prefix: string) {
    return this.name;
  }
}

5.6.抽象类

不能被 new 的 可以采用抽象类,抽象类中可以采用抽象方法 抽象类无法被实例化,只能被继承,抽象方法不能在抽象类中实现,只能在抽象类的具体子类中实现,而且必须实现。

abstract class Animal {
  drink() {
    // 抽象类中可以有非抽象的方法
    console.log("drink");
  }
  name!: string;
  abstract eat: () => void; // 实例方法
  abstract speak(): void; // 原型方法 需要子类来实现 speak 方法
}
class Cat extends Animal {
  constructor() {
    super();
    this.eat = function () {};
  }
  speak() {
    console.log("猫猫叫");
  }
}
class Dog extends Animal {
  speak(): string {
    console.log("汪汪叫");
    return "wangwang";
  }
}

定义类型时 void 表示函数的返回值为空(不关心返回值类型,所有在定义函数时也不关心函数返回值类型)

6.接口

接口: 接口形状(对象、类、函数、混合类型); 定义一些还没有实现的内容 接口可以在面向对象编程中表示行为的抽象,也可以描述对象的形状(结构)。 接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。 (接口中不能含有具体的实现逻辑)

type 和 interface 的区别: interface 通常描述 对象、类的结构比较多; type 来描述函数的签名、联合类型、工具类型、联合类型、映射条件类型

在描述的时候 尽量用 type, 不能用再考虑 interface type 优点: 可以用联合类型; type 不能重名;type 中可以用后续的条件类型、映射 interface 优点: 能重名(即合并)、可以扩展和实现、继承、混合类型

接口最常用的就是描述对象, 可以通过索引操作[] 来访问内部类型

6.1.函数接口参数

const fullName = ({
  firstName,
  lastName,
}: {
  firstName: string;
  lastName: string;
}): string => {
  return firstName + lastName;
};

我们可以约束函数中的参数,但是类型无法复用

// type IFullName = {
// firstName:string;
// lastName:string
// }
interface IFullName {
  firstName: string;
  lastName: string;
}
const fullName = ({ firstName, lastName }: IFullName): string => {
  return firstName + lastName;
};

我们可以通过接口进行描述

6.2.函数类型接口

interface IFullName {
  firstName: string;
  lastName: string;
}
interface IFn {
  (obj: IFullName): string;
}
const fullName: IFn = ({ firstName, lastName }) => {
  return firstName + lastName;
};

通过接口限制函数的参数类型和返回值类型

6.3.函数混合类型

interface ICounter {
  (): number; // 限制函数类型
  count: 0; // 限制函数上的属性
}
let fn: any = () => {
  fn.count++;
  return fn.count;
};
fn.count = 0;
let counter: ICounter = fn;
console.log(counter()); // 1
console.log(counter()); // 2

6.4.对象接口

对象接口可以用来描述对象的形状结构

对象采用接口实现 描述后端返回的数据结构

interface IVegetables {
  // 接口是抽象的没有具体实现
  readonly color: string;
  size: string;
}
interface IVegetables {
  age?: number; // 可选属性
  taste: "sour" | "sweet";
}
const tomato: IVegetables = {
  color: "red",
  size: "10",
  taste: "sour",
};
tomato.color = "green"; // 仅读属性不能进行修改

?标识的属性为可选属性, readOnly 标识的属性则不能修改。多个同名的接口会自动合并

const tomato: IVegetables = {
  color: "red",
  size: "10",
  taste: "sour",
  type: "蔬菜",
} as IVegetables; // 多余的属性可以使用类型断言, 但是不能取多余的属性(因为不知道 有没有)

6.5.任意属性、可索引接口

interface Person {
  // 可以采用任意类型
  name: string;
  [key: string]: any; // 对象的 key: 可以有三种类型 string number symbol
}
let p: Person = {
  name: "zf",
  age: 10,
  [Symbol()]: "回龙观",
};

任意属性可以对某一部分必填属性做限制,其余的可以随意增减

interface IArr {
  [key: number]: any; // 可能是数字索引、数组
}
let p: IArr = {
  0: "1",
  1: "2",
  3: "3",
};
let arr: IArr = [1, "d", "c"];

可索引接口可以用于标识数组

interface Person {
  name: string;
  age: string;
  address: {
    num: number;
  };
  // [key:string]: any // 如果写了任意类型, 则取出的 val 就是任意类型
}
type PersonName = Person["name"]; // string
type PersonNum = Person["address"]["num"]; // number
type PropTypeUnion = keyof Person; // 取 key 'name | age | address'
type PropTypeValueUnion = Person[keyof Person]; // 取值: 'string | {num: 316;}'

6.6.类接口

接口:抽象类(有抽象的和非抽象的) 和 接口(都是抽象)接口没有具体的实现; 描述类中的 属性和方法 这里先来强调一下抽象类和接口的区别,抽象类中可以包含具体方法实现。接口中不能包含实现

interface Speakable {
  name: string;
  speak(): void; // 原型方法
  // speak: () => void; // 实例方法
}
interface ChineseSpeakable {
  speakChinese(): void;
}
class Speak implements Speakable, ChineseSpeakable {
  // 实现某个类型 implements 抽象类 即关键字
  name!: string;
  speak() {}
  speakChinese() {}
}

一个类可以实现多个接口,在类中必须实现接口中的方法和属性

6.7.接口继承

interface Speakable {
  speak(): void;
}
interface SpeakChinese extends Speakable {
  speakChinese(): void;
}
class Speak implements SpeakChinese {
  speakChinese(): void {
    throw new Error("Method not implemented.");
  }
  speak(): void {
    throw new Error("Method not implemented.");
  }
}

6.8.构造函数类型

interface Clazz {
  new (name: string): any;
}
function createClass(target: Clazz, name: string) {
  return new target(name); // 传入的是一个构造函数
}
class Animal {
  constructor(public name: string) {
    this.name = name;
  }
}
let r = createClass(Animal, "Tom");

这里无法标识返回值类型

interface Clazz<T> {
  new (name: string): T;
}
// 泛型类似于函数的参数,泛型的声明一般采用一个大写(开头)字母来表示
function createClass<T>(target: Clazz<T>, name: string): T {
  return new target(name);
}
class Animal {
  constructor(public name: string) {
    this.name = name;
  }
}
let r = createClass(Animal, "Tom"); // createClass<Animal>(Animal, 'Tom') // <Animal>可以省略

new() 表示当前是一个构造函数类型,这里捎带使用了下泛型。 在使用 createClass 时动态传入类型。

// 描述构造函数类型 类类型描述的是实例,想获取到类本身的类型 需要采用 typeof 获取
// typeof Person
// type Clazz = new () => any;

class Person {}
function createInstance(clazz: typeof Person): Person {
  // typeof Person =》 (new () =>any) =》 new (): Person
  return new clazz();
}
let instance = createInstance(Person);

7.泛型

7.1.指定函数参数类型

泛型可以用于 函数 对象 类... 入参与返回值有对应关系

单个泛型

const getArray = <T>(times: number, val: T): T[] => {
  let result: T[] = [];
  for (let i = 0; i < times; i++) {
    result.push(val);
  }
  return result;
};
getArray(3, 3); // 3 => T => number // 当使用的时候 才可以确定类型

多个泛型

function swap<T, K>(tuple: [T, K]): [K, T] {
  // 元组交换
  return [tuple[1], tuple[0]];
}
console.log(swap(["a", "b"]));

7.2.函数标注的方式

类型别名

type TArray = <T, K>(tuple: [T, K]) => [K, T];
const getArray: TArray = <T, K>(tuple: [T, K]): [K, T] => {
  return [tuple[1], tuple[0]];
};

可以使用类型别名,但是类型别名不能被继承和实现。一般联合类型可以使用类型别名来声明

接口

interface IArray {
  <T, K>(typle: [T, K]): [K, T];
}
const getArray: IArray = <T, K>(tuple: [T, K]): [K, T] => {
  return [tuple[1], tuple[0]];
};

const getArray1: TArray = (tuple) => {
  // 函数兼容此类型
  return [tuple[1], tuple[0]];
};

能使用 interface 尽量使用 interface

例:

type ICallback1<T> = (item: T, idx: number) => void; // 使用接口时确定的类型
type ICallback = <T>(item: T, idx: number) => void; // 在调用函数时确定的类型
type IForEach = <T>(arr: T[], callback: ICallback1<T>) => void;
// const forEach:IForEach = (arr, callback) => {};
const forEach = <T>(arr: T[], callback: ICallback1<T>) => {
  // :Icallback
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i], i); // callback 没有执行 所以无法推导 arr[i] = T;
  }
};
forEach([1, 2, 3], function (item, idx) {
  console.log(item, idx);
});

7.3.泛型接口使用

interface ISum<T> {
  // 这里的 T 是使用接口的时候传入
  <U>(a: T, b: T): U; // 这里的 U 是调用函数的时候传入
}
let sum: ISum<number> = (a: number, b: number) => {
  return 3 as any;
};

7.4.默认泛型

type Union<T = string> = T | number;
type T1 = Union; // string | number
type T2 = Union<boolean>; // boolean | number
interface T2<T = string> {
  name: T;
}
type T22 = T2;
let name1: T22 = { name: "zf" };

可以指定泛型的默认类型,方便使用

7.5.类中的泛型

创建实例时提供类型

class MyArray<T> {
  // T => number
  arr: T[] = [];
  add(num: T) {
    this.arr.push(num);
  }
  getMaxNum(): T {
    let arr = this.arr;
    let max = arr[0];
    for (let i = 1; i < arr.length; i++) {
      let current = arr[i];
      current > max ? (max = current) : null;
    }
    return max;
  }
}
let myArr = new MyArray<number>();
myArr.add(3);
myArr.add(1);
myArr.add(2);
console.log(myArr.getMaxNum());

校验构造函数类型

const createClass = <T>(clazz: new (name: string, age: number) => T): T => {
  return new clazz(name, age);
};
createClass<Person2>(Person2);

7.6.泛型约束

泛型必须包含某些属性

interface IWithLength {
  length: number;
}
// 'abc' extends IWithLength 把字符串幻想成一个基于对象扩展的类型
function getLen<T extends IWithLength>(val: T) {
  // T 是 IWithLength 的子类型
  return val.length;
}
getLen("hello");
const sum = <T extends number>(a: T, b: T): T => {
  return (a + b) as T;
};
let r = sum<number>(1, 2);

返回泛型中指定属性

const getVal = <T, K extends keyof T>(obj: T, key: K): T[K] => {
  return obj[key];
};

对象中使用泛型

interface ApiResponse<T = any> {
  code: number;
  data: T;
  message?: string;
}
interface LoginRes {
  token: string;
}
function toLogin(): ApiResponse<LoginRes> {
  return {
    code: 200,
    data: {
      token: "Bearer token",
    },
  };
}

let r = toLogin();
// r.data.token

11. 交叉类型

& 按位与 都是 1 才是 1(都要满足); | 按位或 有一个是 1 就是 1 ;

type Person = number | string; // 联合类型是或者 关系
let p: Person = "1";
let p1: Person = 1;

交叉类型(Intersection Types)是将多个类型合并为一个类型

interface Person1 {
  handsome: string;
}
interface Person2 {
  high: string;
}
type P1P2 = Person1 & Person2;
let p: P1P2 = { handsome: "帅", high: "高" };

举例:我们提供两拨人,一拨人都很帅、另一拨人很高。我们希望找到他们的交叉部分 => 又高又帅的人

生成的交叉类型是 A B 的子类, 内部的嵌套类型也会做交叉类型

interface Person1 {
  handsome: string;
  address: {
    n: string;
  };
}
interface Person2 {
  high: string;
  address: {
    n: number;
  };
}
type P1P2 = Person1 & Person2;
let p: P1P2 = {
  handsome: "帅",
  high: "高",
  address: {
    a: "a", // 报错 never; 交叉类型 交叉出来的结果可以赋予给 A, 也可以赋予给 B
  },
};

type Temp = P1P2["address"]["n"]; // never
function mixin<T, K>(a: T, b: K): T & K {
  return { ...a, ...b };
}
const x = mixin({ name: "zf" }, { age: 11 });

function mixin<T, K>(a: T, b: K): T & K {
  return { ...a, ...b };
}
const x = mixin({ name: "zf", c: 2 }, { age: 11, c: "2" }); // x.c => never
interface IPerson1 {
  name: string;
  age: number;
}

interface IPerson2 {
  name: number;
  age: number;
}
type person = IPerson1 & IPerson2;
let name!: never;
let person: person = { name, age: 11 }; // 两个属性之间 string & number 的值为 never

12.条件类型

12.1.条件类型基本使用

可以使用 extends 关键字和三元表达式,实现条件判断

interface Fish {
  name1: string;
}
interface Water {
  name2: string;
}
interface Bird {
  name3: string;
}
interface Sky {
  name4: string;
}
type Condition<T> = T extends Fish ? Water : Sky;
let con1: Condition<Fish> = { name2: "水" };

type SelectType<T> = T extends Fish ? Water : Sky;
type T9 = SelectType<Bird | Sky>; // Water | Sky
type T10 = Bird | Fish extends Fish ? Water : Sky; // Water | Sky

12.2.条件类型分发

  • 1).通过泛型传入的方式来比较的时候会出现分发
  • 2).类型需要联合类型
  • 3).类型需要完全的裸露出来(裸类型)
let con2: Condition<Fish | Bird> = { name2: "水" };

这里会用每一项依次进行分发,最终采用联合类型作为结果,等价于

type c1 = Condition<Fish>;
type c2 = Condition<Bird>;
type c = c1 | c2;

12.3.内置条件类型

1.Exclude 排除类型

type Exclude<T, U> = T extends U ? never : T;
type MyExclude = Exclude<"1" | "2" | "3", "1" | "2">; // 3

2.Extract 抽取类型

type Extract<T, U> = T extends U ? T : never;
type MyExtract = Extract<"1" | "2" | "3", "1" | "2">; // 2 | 1

3.NoNullable 非空检测

type NonNullable<T> = T extends null | undefined ? never : T;
type MyNone = NonNullable<"a" | null | undefined>; // "a"

12.4.infer 类型推断

infer 关键字 只能用在条件类型中 用来提取类型的某一个部分的类型,放在不同的位置 就可以帮我们取不同位置的类型 1.ReturnType 返回值类型

function getUser(a: number, b: number) {
  return { name: "zf", age: 10 };
}
type ReturnType<T> = T extends (...args: any) => infer R ? R : never;
type MyReturn = ReturnType<typeof getUser>; // {name:string,b:number}

2.Parameters 参数值类型

type Parameters<T> = T extends (...args: infer R) => any ? R : any;
type MyParams = Parameters<typeof getUser>; // [a:number,b:number]

3.ConstructorParameters 构造函数参数类型

class Person {
  constructor(name: string, age: number) {}
}
type ConstructorParameters<T> = T extends { new (...args: infer R): any }
  ? R
  : never;
type MyConstructor = ConstructorParameters<typeof Person>; //[name:string,age:number]

4.InstanceType 实例类型

type InstanceType<T> = T extends { new (...args: any): infer R } ? R : any;
type MyInstance = InstanceType<typeof Person>;

12.5.infer 实践

将数组类型转化为联合类型

type ElementOf<T> = T extends Array<infer E> ? E : never;
type TupleToUnion = ElementOf<[string, number, boolean]>; // string | number | boolean

将两个函数的参数转化为交叉类型

type T1 = { name: string };
type T2 = { age: number };
type ToIntersection<T> = T extends [(x: infer U) => any, (x: infer U) => any]
  ? U
  : never;
type t3 = ToIntersection<[(x: T1) => any, (x: T2) => any]>;

表示要把 T1、T2 赋予给 x,那么 x 的值就是 T1、T2 的交集。(参数是逆变的可以传父类) TS 的类型:TS 主要是为了代码的安全性来考虑。所以所有的兼容性问题都要从安全性来考虑!

  • never 和 字面量 的关系; never 是任何类型的子类 type T1 = never extends '123' ? true : false;
  • 字面量类型 和 基础类型的关系; 自面量类型属于基础类型的子类 type T2 = 123 extends number ? true : false;
  • 基础类型 和 包装类型; 基础类型是包装类型的子类型 type T3 = string extends String ? true : false;
  • 所有包装的上一层是 Object; {}(字面量类型) == new Object(); // 万物皆对象 type T4 = String extends Object ? true : false;
  • any 所有的类型都是 any 和 unknow 的子类型 // type T6 = Object extends any ? true : false; unknow 可以替代 any;
  • type T7 = any extends 1 ? true : false; true; // any 可以看成是 1+其它类型(内置有分发的机制) 直接取最终的联合类型
  • type T77 = any extends 1 ? 1 : 2; // 1 | 2; null 只能赋给 null; undefined 同理 (undefined extends void) => true
  • type T8 = unknow extends 1 ? true : false; false;

// extends 父子关系

type T1 = never extends "123" ? true : false; // 子类可以赋予给父类型, never 是任何类型的子类
let a: number = (function (): never {
  throw new Error();
})();

8、兼容性

TS 中的兼容性,主要看结构是否兼容。(核心是考虑安全性)

基本数据类型的兼容性

let temp: string | number;
let num!: number;
temp = num;
let num: {
  toString(): string;
};
let str: string = "zf";
num = str; // 字符串中具备toString()方法,所以可以进行兼容

接口兼容性

接口的兼容性,只要满足接口中所需要的类型即可

interface IAnimal {
  name: string;
  age: number;
}
interface IPerson {
  name: string;
  age: number;
  address: string;
}
let animal: IAnimal;
let person: IPerson = {
  name: "zf",
  age: 11,
  address: "回龙观",
};
animal = person;

函数兼容性

函数的兼容性主要是比较参数和返回值

  • 参数
let sum1 = (a: string, b: string) => a + b;
let sum2 = (a: string) => a;
sum1 = sum2;

赋值函数的参数要少于等于被赋值的函数,与对象相反,例如:

type Func<T> = (item: T, index: number) => void;
function forEach<T>(arr: T[], cb: Func<T>) {
  for (let i = 0; i < arr.length; i++) {
    cb(arr[i], i);
  }
}
forEach([1, 2, 3], (item) => {
  console.log(item);
});
  • 返回值
type sum1 = () => string | number;
type sum2 = () => string;

let fn1: sum1;
let fn2!: sum2;
fn1 = fn2;

函数的逆变与协变

函数的参数是逆变的,返回值是协变的 (在非严格模式下函数的参数是双向协变的)

class Parent {
  address: string = "回龙观";
}
class Child extends Parent {
  money: number = 100;
}
class Grandsom extends Child {
  name: string = "吉姆";
}
type Callback = (person: Child) => Child;
function execCallback(cb: Callback) {}
let fn = (person: Parent) => new Grandsom();
execCallback(fn);
// 通过这个案例可以说明,函数参数可以接收父类,返回值可以返回子类

类的兼容性

class Perent {
  name: string = "zf";
  age: number = 11;
}
class Parent1 {
  name: string = "zf";
  age: number = 11;
}
let parent: Perent = new Parent1();

这里要注意的是,只要有 private 或者 protected 关键字类型就会不一致;但是继承的类可以兼容

class Parent1 {
  protected name: string = "zf";
  age: number = 11;
}
class Child extends Parent1 {}
let child: Parent1 = new Child(); // 只有age

泛型的兼容性

interface IT<T> {}
let obj1: IT<string>;
let obj2!: IT<number>;
obj1 = obj2;

枚举的兼容性

enum USER1 {
  role = 1,
}
enum USER2 {
  role = 1,
}
let user1!: USER1;
let user2!: USER2;
user1 = user2; // 错误语法
// 不同的枚举类型不兼容

9、类型保护

通过判断识别所执行的代码块,自动识别变量属性和方法

typeof 类型保护

function double(val: number | string) {
  if (typeof val === "number") {
    val;
  } else {
    val;
  }
}

instanceof 类型保护

class Cat {}
class Dog {}

const getInstance = (clazz: { new (): Cat | Dog }) => {
  return new clazz();
};
let r = getInstance(Cat);
if (r instanceof Cat) {
  r;
} else {
  r;
}

in 类型保护

interface Fish {
  swiming: string;
}
interface Bird {
  fly: string;
  leg: number;
}
function getType(animal: Fish | Bird) {
  if ("swiming" in animal) {
    animal; // Fish
  } else {
    animal; // Bird
  }
}

可辨识联合类型

interface WarningButton {
  class: "warning";
}
interface DangerButton {
  class: "danger";
}
function createButton(button: WarningButton | DangerButton) {
  if (button.class == "warning") {
    button; // WarningButton
  } else {
    button; // DangerButton
  }
}

null 保护

const addPrefix = (num?: number) => {
  num = num || 1.1;
  function prefix(fix: string) {
    return fix + num?.toFixed();
  }
  return prefix("zf");
};
console.log(addPrefix());

// 这里要注意的是ts无法检测内部函数变量类型

自定义类型保护

interface Fish {
  swiming: string;
}
interface Bird {
  fly: string;
  leg: number;
}
function isBird(animal: Fish | Bird): animal is Bird {
  return "swiming" in animal;
}
function getAniaml(animal: Fish | Bird) {
  if (isBird(animal)) {
    animal;
  } else {
    animal;
  }
}

完整性保护

interface ICircle {
  kind: "circle";
  r: number;
}
interface IRant {
  kind: "rant";
  width: number;
  height: number;
}
interface ISquare {
  kind: "square";
  width: number;
}
type Area = ICircle | IRant | ISquare;
const isAssertion = (obj: never) => {};
const getArea = (obj: Area) => {
  switch (obj.kind) {
    case "circle":
      return 3.14 * obj.r ** 2;
    default:
      return isAssertion(obj); // 必须实现所有逻辑
  }
};

10、类型推断

赋值推断

赋值时推断,类型从右像左流动,会根据赋值推断出变量类型

let str = "zf";
let age = 11;
let boolean = true;

返回值推断

自动推断函数返回值类型

function sum(a: string, b: string) {
  return a + b;
}
sum("a", "b");

函数推断

函数从左到右进行推断

type Sum = (a: string, b: string) => string;
const sum: Sum = (a, b) => a + b;

属性推断

可以通过属性值,推断出属性的类型

let person = {
  name: "zf",
  age: 11,
};
let { name, age } = person;

类型反推

可以使用 typeof 关键字反推变量类型

let person = {
  name: "zf",
  age: 11,
};
type Person = typeof person;

索访问引操作符

interface IPerson {
  name: string;
  age: number;
  job: {
    address: string;
  };
}
type Job = IPerson["job"]; // {address:string}

类型映射

interface IPerson {
  name: string;
  age: number;
}
type MapPerson = { [key in keyof IPerson]: IPerson[key] };

13、内置类型

Partial 转化可选属性

interface Company {
  num: number;
}
interface Person {
  name: string;
  age: string;
  company: Company;
}
// type Partial<T> = { [K in keyof T]?: T[K] }; 实现原理
type PartialPerson = Partial<Person>;

// 遍历所有的属性将属性设置为可选属性,但是无法实现深度转化!
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
type DeepPartialPerson = DeepPartial<Person>;

// 我们可以实现深度转化,如果值是对象继续深度转化。

Required 转化必填属性

// 将所有的属性转化成必填属性
interface Company {
  num: number;
}
interface Person {
  name: string;
  age: string;
  company: Company;
}
type PartialPerson = Partial<Person>;
type Required<T> = { [K in keyof T]-?: T[K] };
type RequiredPerson = Required<PartialPerson>;

Readonly 转化仅读属性

type Readonly<T> = { readonly [K in keyof T]: T[K] };
type RequiredPerson = Readonly<Person>;

// 将所有属性变为仅读状态

Pick 挑选所需的属性

type Pick<T, U extends keyof T> = { [P in U]: T[P] };
type PickPerson = Pick<Person, "name" | "age">;

// 在已有类型中挑选所需属性

Record 记录类型

type Record<K extends keyof any, T> = { [P in K]: T }; // any = {key:T}
let person: Record<string, any> = { name: "zf", age: 11 };
// 实现map方法, 我们经常使用record类型表示映射类型
function map<T extends keyof any, K, U>(
  obj: Record<T, K>,
  callback: (item: K, key: T) => U
) {
  let result = {} as Record<T, U>;
  for (let key in obj) {
    result[key] = callback(obj[key], key);
  }
  return result;
}
const r = map({ name: "zf", age: 11 }, (item, key) => {
  return item;
});

Omit 忽略属性

let person = {
  name: "zf",
  age: 11,
  address: "回龙观",
};
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type OmitAddress = Omit<typeof person, "address">; // {name:string,age:number}

// 忽略person中的address属性 (先排除掉不需要的key,在通过key选出需要的属性)

15、自定义类型

Diff 实现

求两个对象不同的部分

let person1 = {
  name: "zf",
  age: 11,
  address: "回龙观",
};
let person2 = {
  address: "回龙观",
};
type Diff<T extends object, K extends Object> = Omit<T, keyof K>; // Omit 忽略属性
type DiffPerson = Diff<typeof person1, typeof person2>; // {name:string,age:number}

InterSection 交集

let person1 = {
  name: "zf",
  age: 11,
  address: "回龙观",
};
let person2 = {
  address: "回龙观",
};
type InterSection<T extends object, K extends object> = Pick<
  T,
  Extract<keyof T, keyof K>
>;
type InterSectionPerson = InterSection<typeof person1, typeof person2>; // {'address': string}

Overwrite 属性覆盖

type OldProps = { name: string; age: number; visible: boolean };
type NewProps = { age: string; other: string };

type Diff<T extends object, K extends Object> = Omit<T, keyof K>; //{name:string,visible:boolean}
type InterSection<T extends object, K extends object> = Pick<
  T,
  Extract<keyof T, keyof K>
>; // {age:number}
type Overwrite<
  T extends object,
  K extends object,
  I = Diff<T, K> & InterSection<K, T> // {name:string,age:string,visible:boolean}
> = Pick<I, keyof I>;
type ReplaceProps = Overwrite<OldProps, NewProps>;

Merge 对象合并

type Compute<A extends any> = { [K in keyof A]: A[K] };
type Merge<T, K> = Compute<Omit<T, keyof K> & K>; // Omit: {name:string,visible:boolean}
type MergeObj = Merge<OldProps, NewProps>; // {name:string,visible:boolean,age:string,other:string}
// 将两个对象类型进行合并操作

16、unknown

unknown 类型

unknown 类型,任何类型都可以赋值为 unknown 类型。它是 any 类型对应的安全类型

let unknown: unknown;
unknown = "zf";
unknown = 11;

// 不能访问unknown类型上的属性,不能作为函数、类来使用

· 联合类型中的 unknown

type UnionUnknown = unknown | null | string | number; // unknown

// 联合类型与unknown都是unknown类型

· 交叉类型中的 unknown

type inter = unknown & null; // null
// 交叉类型与unknown都是其他类型

unknown 特性

· never 是 unknown 的子类型

type isNever = never extends unknown ? true : false;

· keyof unknown 是 never

type key = keyof unknown;

· unknown 类型不能被遍历

type IMap<T> = {
  [P in keyof T]: number;
};
type t = IMap<unknown>;

17.模块和命名空间

默认情况下 ,我们编写的代码处于全局命名空间中

文件模块: 如果在你的 TypeScript 文件的根级别位置含有 import 或者 export,那么它会在这个文件中创建一个本地的作用域 。

// a.ts导出
export default "zf";

// index.ts导入
import name from "./a";

命名空间

| 命名空间可以用于组织代码,避免文件内命名冲突 · 命名空间的使用

export namespace zoo {
  export class Dog {
    eat() {
      console.log("zoo dog");
    }
  }
}
export namespace home {
  export class Dog {
    eat() {
      console.log("home dog");
    }
  }
}

let dog_of_zoo = new zoo.Dog();
dog_of_zoo.eat();
let dog_of_home = new home.Dog();
dog_of_home.eat();

· 命名空间嵌套使用

export namespace zoo {
  export class Dog {
    eat() {
      console.log("zoo dog");
    }
  }
  export namespace bear {
    export const name = "熊";
  }
}
console.log(zoo.bear.name);

| 命名空间中导出的变量可以通过命名空间使用。

18、类型声明

声明全局变量

· 普通类型声明

declare let age: number;
declare function sum(a: string, b: string): void;
declare class Animal {}
declare const enum Seaons {
  Spring,
  Summer,
  Autumn,
  Winter,
}
declare interface Person {
  name: string;
  age: number;
}