Typescript 速记(备忘)手册

73 阅读13分钟

描述基本类型

const x: string = "john"; // 表示 x 必须是 string类型 , 且 x 的值不允许修改
var x: number; // 表示 x 必须是 number 类型 , 且 x 的值可以修改
let x: boolean; // 表示 x 必须是 boolean 类型 , 且 x 的值可以修改
// 还有undefined, null等类型

描述一个对象(或类的实例)

// 声明变量时就定义类型:
const x : {
  label: string;  // 目标必须包含 label 属性 , 且值必须是 string 类型
  color?: string;  // 目标包含或者不包含 color 属性均可, 如果有该属性, 值必须是 string 类型
  readonly age: number; // 目标必须包含 age 属性, 且值必须是 number 类型, 该值不允许修改
  add: (x:number, y:number): boolean; // 目标必须包含一个 add 属性, 且add属性值是一个函数
  [propName: string]: any; // 目标可以包含除上面三个以外的任意属性
} = {
  // ...
}

// 等同于
interface Person {
  label: string;
  color?: string;
  readonly age: number;
  add: (x:number, y:number): boolean;
  [propName: string]: any;
}
let x:Person = {
  //  ...
}

// 等同于
type Person = {
  label: string;
  color?: string;
  readonly age: number;
  add: (x:number, y:number): boolean;
  [propName: string]: any;
}
let x:Person = {
  //  ...
}

描述混合(联合)类型

ts 使用|来表示

type p = string | number;

// -------
let x: p;

类型p表示变量x的值只能是string或者number类型

描述一个函数的参数与返回值

function add(name: string, score: number, color?: string): boolean {
  return true;
}

这个函数中:

  1. add 函数调用时必须接受两个参数
  2. add 函数第一个参数必须是 string 类型
  3. add 函数第二个参数必须是 number 类型
  4. add 函数第三个参数是可选的, 且必须是 string 类型
  5. add 函数不允许有第四个参数
  6. add 函数返回值必须是 boolean 类型

描述一个函数的实现

interface SomeFn {
  (name: string, score: number): boolean;
  // (source...)前面千万不能有名字,有名字就表示描述一个对象了
}
// 等同于
type SomeFn = (name: string, score: number) => boolean;
// 注意这里 boolean 前面是 =>  不是 冒号

let x: SomeFn;

类型SomeFn表示:

  1. 函数x的返回值必须是boolean类型
  2. 函数x如果有第一个参数, 必须是string类型
  3. 函数x如果有第二个参数, 必须是number类型
  4. 函数x不允许有第三个参数

描述自身带有属性的函数

函数除了可以被调用, 函数自身也是可以添加属性的

使用type实现不了这个功能, 需要使用interface:

interface SomeFn {
  description: string; // 表示函数 x 上可以添加description属性
  (source: string, subString: string): boolean;
}

let x: SomeFn;

x.description = "这是一个函数本身属性";

类型SomeFn中的description表示:

  1. 函数x可以添加description属性
  2. 函数xdescription属性值必须是string类型

数组

描述一个全部元素是基本类型的数组

type p = string[];

// 等同于
type p = Array<string>;

// 等同于
interface p {
  [string: number]: string;
}

// 等同于
type p = {
  [string: number]: string;
};

// ------------------------------

let x: p;

类型p表示:

  1. 变量 x 必须是数组,
  2. 变量 x的所有元素必须是 string 类型

描述一个全部元素是对象类型的数组

interface Person {
  name: string;
  age: number;
}
type p = Person[];

// 等同于
type p = Array<Person>;

// 等同于
interface p {
  [string: number]: Person;
}

// 等同于
type p = {
  [string: number]: Person;
};

// ------------------------------

let x: p;

类型p表示:

  1. 变量 x 必须是数组,
  2. 变量 x的所有元素必须是 Person 类型

描述一个全部元素是混合类型的数组

type StrOrNum = string | number;
type p = StrOrNum[];

// 等同于
type p = Array<StrOrNum>; // 等同于 type p = Array<string | number>;

// 等同于
interface p {
  [string: number]: StrOrNum; // 等同于 [string: number]: string | number
}

// 等同于
type p = {
  [string: number]: StrOrNum;
};

// ------------------------------

let x: p;

类型p表示:

  1. 变量 x 必须是数组,
  2. 变量 x的所有元素必须是 stringnumber 类型

描述一个只读类型的数组

type p = readonly string[];

// 等同于,  不建议使用
interface p {
  readonly [string: number]: string;
}

// 等同于,  不建议使用
type p = {
  readonly [string: number]: string;
};

// -----------
const x: p = ["john", "lily"];

类型p表示:

  1. 变量 x 必须是数组,
  2. 变量 x的所有元素必须是 string 类型
  3. 变量 x必须在声明的时候立即赋值, 且不允许修改内部元素的值

描述一个包含多种类型元素的数组(元组)

type p = [string, number, boolean];

// -------------
let x: p;

这表示一种特殊的元组类型, 它确切的知道每个元素的类型

class 相关

描述 class 中的属性

interface P1 {}
interface P2 {}
class Account {}
class User extends Account implements P1, P2 {
  id: string; // 表示属性 id 为 string 类型
  displayName?: boolean; // 表示可选的属性, boolean 类型
  name!: string; // 表示 name 属性在运行时一定是有值的(非null, 非undefined)
  #arrtibutes: Map<any, any>; // 表示一个私有属性, 只能在类的内部访问, 实例无法访问
  roles = ["user"]; // 表示一个带有默认值的属性
  readonly createAt = new Date(); // 表示一个只读属性

  // 下面是两种声明函数以及定义参数与返回值类型的方式
  setName(name: string): void {
    this.name = name;
  }
  verifyName = (name: string): boolean => {
    return this.name === name;
  };

  // 下面是ts中关于函数重载的示例
  // run(): void {}
  // run(options: Map<any, any>): void {}
  // run(options: Map<any, any>, cb: Function): void {}

  // 下面是class中的getter与setter
  get accountId(): string {
    return "";
  }
  set accountId(id: string) {
    this.id = id;
  }

  // 下面是ts中属性修饰器的示例, 修饰器仅用于ts检查
  private makeRequest() {} // 私有属性, 只能在本class的实例中调用
  protected handRequest() {} // 保护性属性, 只能在本class与子类的实例中调用

  // 下面是静态属性与静态方法的示例
  static #userCount = 0; // 只能通过类本身访问, 不能通过实例访问
  static registerUser() {
    // 只能通过类本身访问, 不能通过实例访问
  }

  static {
    this.#userCount++;
    // 这里是静态代码块
    // 这里的代码会自动执行, 不需要等待类被实例化
  }
}
const p = new User();

描述一个 class 的实现

interface Runnable {
  name: string;
  run(distance: string, time: number): boolean;
}

class Person implements Runnable {
  name: string;
  run(distance: string, time: number): boolean {
    return true;
  }
}

类型Runnable表示:

  1. 如果没有定义name属性 ts 会抛出错误
  2. 如果name属性的值不是 string 类型, ts 会抛出错误
  3. 如果没有定义run属性, ts 会抛出错误
  4. 如果 run 不是一个函数, ts 会抛出错误
  5. 如果 run 函数中已定义的参数类型顺序未与 (string, number) 保持一致, ts 会抛出错误
  6. 如果 run 函数的参数数量超过 (distance: string, time: number), ts 会抛出错误
  7. 如果 run 的返回值不是 boolean 类型, ts 会抛出错误

描述一个class类型的参数

interface PersonClass {
  // 注意这里有一个 new 关键字, 表示该interface定义的是class本身, 而不是class的实现
  new (name: string, age: number, adult: boolean);
}

function generatePerson(
  p: PersonClass, // 也可以简写为 p: { new (name: string, age: number, adult: boolean)}
  name: string,
  age: number,
  adult?: boolean
) {
  // 这里要求参数 p 必须是一个class,
  return new p(name, age, adult);
}

class Person {
  // ...
}
generatePerson(Person, "lily", 30);

类型PersonClass表示:

  1. 函数generatePerson调用时,第一个参数必须是一个class
  2. 如果该class内部有construct函数, 且construct有参数,那么construct的参数类型必须满足:
    1. 如果有第一个参数, 必须是string类型
    2. 如果有第二个参数, 必须是number类型
    3. 如果有第三个参数, 必须是boolean类型
    4. 不允许有第四个参数

类型扩展(extends&)

interface Animal {
  name: string;
}

interface Color {
  color: string;
}

// interface使用extends扩展类型
interface Bear extends Animal, Color {
  honey: boolean;
}

// 等同于:
type Bear = Animal &
  Color & {
    honey: boolean;
  };

type 与 interface 的区别

  1. interface只能用来定义{}格式的类型, 而type可以定义任何类型
  2. interface可以通过多次声明来扩展属性, 而type是不可扩展的(被 type 定义的类型是被锁死的, 不能添加新属性)
  3. 需要使用继承时, interfaceextendstype&在类型检查器中运行速度稍微快一些
  4. 相同名称的interface在同一个作用域下会被合并, 可能导致预料之外的问题

何时用 type,何时用 interface

  1. 官方推荐interface, 但个人建议使用type更合适, 因为可以避免上面的第 4 条问题
  2. 需要使用继承关系时, 还是推荐使用interface

泛型

函数中的泛型

function add<T>(v: T): T[] {
  return new Array(3).fill(v);
}

const x = add(30);
const y = add<string>("30");

上面的案例:

  1. const x = add(30);T的类型取决于add函数调用时参数v的类型, 所以返回值是number[]类型
  2. const y = add<string>("30")T的类型已经被锁定string类型, 所以参数v必须是string类型, 返回值是string[]类型, 否则会抛出错误

函数类型中的泛型

type Run<T> = (scores: T[]) => T;

// 等同于
interface Run<T> {
  (score: T[]): T;
}

// 用法:
const x: Run<number> = function (scores: number[]): number {
  return Math.max(...scores);
};

上面的案例中, type Run<T> = (scores: T[]) => T 表示:

  1. Run类型的函数在定义时必须指定T的具体类型
  2. Run类型的函数在定义时如果有第 1 个参数, 该参数必须是T组成的数组
  3. Run类型的函数在定义时返回值必须也是T类型

x: Run<number> 表示:

  1. 函数x中的T已经被锁定number类型
  2. 函数x的参数必须是number组成的数组
  3. 函数x的返回值必须也是number类型

class 中的泛型

class Books<T> {
  list: T[];
  constructor() {}
  add(v: T): T[] {
    return this.list;
  }
}

class Animal {} // 动物类
class Plant {} // 植物类

const x1 = new Books<Animal>();
const x2 = new Books<Plant>();

上面的案例中,

  1. 使用构造函数new Book时, 必须提前指定T具体的类型(锁定泛型类型), 否则 ts 会抛出错误
  2. new Books<Animal>()T被锁定为Animal类型,add函数调用时, 参数也必须是Animal的实例
  3. new Books<Plant>()T被锁定为Plant类型,add函数调用时, 参数也必须是Plant的实例

interface 中的泛型

interface MysqlOption {
  // ...
  url: string;
  host: string;
}

interface TypeOrmOptions<T> {
  options: T;
}

const x: TypeOrmOptions<MysqlOption> = {
  options: {
    url: "",
    host: "",
  },
};

上面案例中, 声明x时, 必须提前指定T具体的类型(锁定泛型类型), ts 就可以检查到options内的属性是否正确

泛型约束

函数中的泛型约束

type Base = {
  length: number;
  age?: number;
};

function add<T extends Base>(x: T): boolean {
  return true;
}

add({ name: "john", length: 10 });

上面案例中<T extends Base>就是约束条件, 这个条件要求add函数调用时, 参数:

  1. 必须有length属性, 且属性值必须是number类型
  2. 如果有age属性, 属性值必须是number类型

函数类型中的泛型约束

type Logger<T extends { time: string }> = (content: T) => T;

type Log = {
  msg: string;
  time: string;
};

const x: Logger<Log> = function (content: Log): Log {
  return {} as Log;
};

上面的案例中, T extends { time: string }就是约束条件, 整个语句表示:

  1. 函数x的参数必须包含time属性.
  2. 函数x的参数中的time属性值必须是string类型
  3. 函数x的参数被锁定为Log类型
  4. 函数x的返回值必须是Log类型

keyof 运算符

使用 keyof 从已知类型中获取新的类型

type P1 = { x: string; y: number };
type P2 = keyof P1; // 相当于`type P2 = string | number`

type P1 = { [x: number]: any };
type P2 = keyof P1; // 相当于`type P2 = number`

type P1 = { [x: string]: any };
type P2 = keyof P1; // 相当于`type P2 = string | number` , 因为在JS中 obj[0]和obj['0']是一样的

使用 keyof 从指定对象中获取新的类型

const person = {
  name: "320",
  age: 20,
  goods: [],
};

type p = typeof person;

// `typeof` 从person中自动推断类型
//  相当于
type p = {
  name: string;
  age: number;
  goods: any[];
};

使用 ReturnType 从函数中获取新的类型

type Predicate = (x: unknown) => boolean;
type K = ReturnType<Predicate>; // 相当于 type K = boolean

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

// ReturnType<typeof f> 从函数f中自动推断类型
// 相当于
type P = { x: number; y: number };

从已知类型中映射新的类型 1

先来个简单的:

type p1 = {
  name: string;
  age: number;
  goods: string[];
};

type MapType<t> = {
  [prop in keyof t]: string;
};

// MapType 可以看做是声明了一个函数
// t 可以看做是一个参数,可以是任意字符, 这个参数必须是一个ts类型
// [prop in keyof t]  可以看做类似于JS中的 (k in obj), prop可以是任意字符
type p2 = MapType<p1>;

// 相当于
type p2 = {
  name: string;
  age: string;
  goods: string;
};

上面案例中, p2p1为基础, 把所有属性的类型都改为string

从已知类型中映射新的类型 2

type p1 = {
  name: string;
  age: number;
  goods: string[];
};

type MapType<t> = {
  [prop in keyof t]: (param: t[prop]) => t[prop];
};

// param 可以看做是自定义的形参, 可以是任意字符
type p2 = MapType<p1>;

// 相当于
type p2 = {
  name: (param: string) => string;
  age: (param: number) => number;
  goods: (param: string[]) => string[];
};

上面案例中, p2p1为基础:

  1. name的类型从string改为返回string类型的函数
  2. age的类型从number改为返回number类型的函数
  3. goods的类型从string[]改为返回string[]类型的函数

使用条件表达式获取类型

type ConditionMap<t> = t extends { length: number } ? t : never;

type p1 = ConditionMap<{ name: string }>;
// 相当于
type p1 = never;

// ------------------

type p2 = ConditionMap<{ name: string; length: number }>;
// 相当于
type p2 = {
  name: string;
  length: number;
};

上面的案例中:

  1. ConditionMap可以看做是声明了一个函数
  2. t可以看作是函数的参数, 但是这个参数必须是一个 ts 类型
  3. t extends {length: number} 用来判断参数t中是否包含length:number属性
  4. 如果满足上一条, 返回t本身, 否则返回never

模板文字类型

type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";

type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;

类型AllLocaleIDs相当于:

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

内置字符串运算符

ts 内置了 4 个字符串运算符Uppercase Lowercase Captialize Uncaptialize:

type p1 = Uppercase<"Id" | "Name">; // 全部转为大写, 相当于 p1 = "ID" | "NAME"
type p2 = Lowercase<"Id" | "Name">; // 全部转为小写, 相当于 type p2 = "id" | "name"
type p3 = Capitalize<"id" | "name">; //  首字母转为大写, 相当于 type p3 = "Id" | "Name"
type p4 = Uncapitalize<"Id" | "Name">; // 取消首字母大写, 相当于 type p4 = "id" | "name"

自定义字符串运算符

// 这里的 Str 相当于一个参数
type PreTable<Str extends string> = `user_${Str}`;

type p = PreTable<"id" | "name">; // 相当于 type p = "user_id" | "user_name"

namespace

主要目的是帮助组织和封装代码,以避免全局命名冲突,并实现一种逻辑上的代码分组

namespace N1 {
  export type F1 = (v: string) => void;
}

namespace N1 {
  export type F2 = (v: string) => void;
}

const x: N1.F1 = (v: string) => {};

上面的案例中:

  1. 命名空间N1可以跨文件定义, 类似于interface可以被跨文件扩展属性
  2. 通过.访问命名空间内的某个类型

declare 声明

本节中的index.d.ts一定要添加到tsconfig.json.includes选项中, 否则不会生效

{
  "files": [
    "main.ts",
  ],
  "include": [
    "index.d.ts",
  ],
}

给全局属性添加 ts 支持

假设有以下代码:

// main.ts
function foo() {
  console.log(GlobalEnv.foo);
}

GlobalEnv是一个全局属性, 如果没有声明, ts 会抛出错误

可以在项目的 ts 声明文件index.d.ts中添加如下代码:

// types/index.d.ts
declare var GlobalEnv: {
  foo: string;
};

给没有类型说明文件的第三方模块添加类型说明

假设有一个foo模块:

function init() {
  console.log("init");
}
exports.init = init;

在 ts 文件中使用:

import foo from "foo"; // TS会提示: 文件“...node_modules/foo/index.d.ts”不是模块

foo.init();

可以在项目的 ts 声明文件index.d.ts中添加如下代码:

declare module "foo" {
  export function init(): void;
}

给已有类型说明文件的第三方模块增加类型

假设有一个模块foo

// node_modules/foo/index.js
function init() {
  console.log("init");
}
exports.init = init;

// node_modules/foo.index.d.ts
export function init(): void;

在 ts 文件中使用:

import foo from "foo";

foo.init();
foo.add(); // TS会提示: 类型“typeof import("foo")”上不存在属性“add”。

可以在项目的 ts 声明文件index.d.ts中添加如下代码:

declare module "foo" {
  export function add(): void;
}

给已有类型说明文件的第三方模块中的某个类型扩展新的属性

假设有一个模块foo

// node_modules/foo/index.js
function init(options) {
  console.log("init", options);
}
exports.init = init;

// node_modules/foo.index.d.ts
export interface OrmOptions {
  name: string;
  url: string;
  port: number;
}
export function init(v: OrmOptions): void;

在 ts 文件中使用:

import foo from "foo";

const x: foo.OrmOptions = {
  name: "mysql",
  url: "loclhost",
  port: 3636,
  driver: "mysql2",
  // TS提示1: 不能将类型“{ name: string; url: string; port: number; driver: string; }”分配给类型“OrmOptions”。
  // TS提示2: 对象字面量只能指定已知属性,并且“driver”不在类型“OrmOptions”中
};
init(option);

可以在项目的 ts 声明文件index.d.ts中添加如下代码, 用来扩展OrmOptions:

import foo from "foo"; // 一定要有这句!
declare module "foo" {
  export interface OrmOptions {
    driver: string;
  }
}