TypeScript类型知识小记

47 阅读5分钟

泛型

泛型表示泛指某一种类型,开发者可以指定一个表示类型的变量,用它来作为实际类型的占位符,用尖括号来包裹类型变量 。泛型的主要作用是创建可重用的组件,从而让一个组件可以支持多种数据类型,它可以作用在接口、类、函数或类型别名上

泛型示例

function add(paramsA: T, paramsB:T):T {
  return paramsA + paramsB;
}

以上函数使用泛型约束了两个参数以及返回值的类型必须是相同的;

其中 T 代表 Type,在定义泛型时通常用作第一个类型变量名称。但实际上 T 可以用任何有效名称代替。除了 T 之外,以下是常见泛型变量代表的意思:

  • K(Key):表示对象中的键类型;

  • V(Value):表示对象中的值类型;

  • E(Element):表示元素类型。

使用泛型的场景:

  • 当你的函数、接口或类将处理多种数据类型时;
  • 当函数、接口或类在多个地方使用该数据类型时。

泛型接口

要解决函数中返回多种类型对象的问题,我们可以创建一个用于的 identity 函数通用 Identities 接口:

interface Identities<V, M> {
  value: V,
  message: M
}

function identity<T, U> (value: T, message: U): Identities<T, U> {
  console.log(value + ": " + typeof (value));
  console.log(message + ": " + typeof (message));
  let identities: Identities<T, U> = {
    value,
    message
  };
  return identities;
}

泛型类

在类中使用泛型也很简单,我们只需要在类名后面,使用 <T, ...> 的语法定义任意多个类型变量,具体示例如下:

interface GenericInterface<U> {
  value: U
  getIdentity: () => U
}

class IdentityClass<T> implements GenericInterface<T> {
  value: T

  constructor(value: T) {
    this.value = value
  }

  getIdentity(): T {
    return this.value
  }
}

const myNumberClass = new IdentityClass<number>(68);
console.log(myNumberClass.getIdentity()); // 68

const myStringClass = new IdentityClass<string>("Semlinker!");
console.log(myStringClass.getIdentity()); // Semlinker!

泛型参数默认类型

泛型参数默认类型与普通函数默认值类似,对应的语法很简单,即 <T=Default Type>,对应的使用示例如下:

interface Person<T=string> {
  id: T;
}

泛型参数的默认类型遵循以下规则:

  • 有默认类型的类型参数被认为是可选的。
  • 必选的类型参数不能在可选的类型参数后。
  • 如果类型参数有约束,类型参数的默认类型必须满足这个约束。
  • 当指定类型实参时,你只需要指定必选类型参数的类型实参。未指定的类型参数会被解析为它们的默认类型。
  • 如果指定了默认类型,且类型推断无法选择一个候选类型,那么将使用默认类型作为推断结果。
  • 一个被现有类或接口合并的类或者接口的声明可以为现有类型参数引入默认类型。
  • 一个被现有类或接口合并的类或者接口的声明可以引入新的类型参数,只要它指定了默认类型。

泛型约束

extends

主要用于检查当前类型是否包含继承的原始类型;

interface Length {
  length: number;
}

function identity<T extends Length>(arg: T): T {
  console.log(arg.length); // 可以获取length属性
  return arg;
}

T extends Length 用于告诉编译器,我们支持已经实现 Length 接口的任何类型。之后,当我们使用不含有 length 属性的对象作为参数调用 identity 函数时,TypeScript 会提示相关的错误信息:

keyof

该操作符可以用于获取某种类型的所有键,其返回类型是联合类型。

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

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

通过 keyof 操作符,我们就可以获取指定类型的所有键,之后我们就可以结合前面介绍的 extends 约束,即限制输入的属性名包含在 keyof 返回的联合类型中。具体的使用方式如下:

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

let tsInfo = {
   name: "Typescript",
   supersetOf: "Javascript",
   difficulty: 1
}


let difficulty: number = 
  getProperty(tsInfo, 'difficulty'); // OK

let supersetOf: string = 
  getProperty(tsInfo, 'superset_of'); // Error

很明显通过使用泛型约束,在编译阶段我们就可以提前发现错误,大大提高了程序的健壮性和稳定性。接下来,我们来介绍一下泛型参数默认类型。

泛型条件类型

T extends U ? X : Y

泛型工具类型

Partial

Partial 的作用就是将某个类型里的属性全部变为可选项 ?。

定义:

type Partial<T> = {
  [P in keyof T]?: T[P];
};

示例:

interface Todo {
  title: string;
  description: string;
}

function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return { ...todo, ...fieldsToUpdate };
}

const todo1 = {
  title: "Learn TS",
  description: "Learn TypeScript"
};

const todo2 = updateTodo(todo1, {
  description: "Learn TypeScript Handbook"
});
Required

作用:生成一个新类型,该类型与 T 拥有相同的属性,但是所有属性皆为必选项

定义

type Require<T> = {
    [p in keyof T]-?: T[P]
}

示例

interface Foo {
    name: string
    age?: number
}

type Bar = Required<Foo>

// 相当于
type Bar = {
    name: string
    age: number
}
Record

Record<K extends keyof any, T> 的作用是将 K 中所有的属性的值转化为 T 类型

定义

type Record<K extends keyof any, T> = {
  [P in K]: T;
};

示例

interface PageInfo {
  title: string;
}

type Page = "home" | "about" | "contact";

const x: Record<Page, PageInfo> = {
  about: { title: "about" },
  contact: { title: "contact" },
  home: { title: "home" }
};
Exclude

Exclude<T, U> 的作用是将某个类型中属于另一个的类型移除掉。

定义

type Exclude<T, U> = T extends U ? never : T;

示例

type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
Extract

Exclude<T, U> 的作用是将某个类型中不属于另一个的类型移除掉。

定义

type Extract<T, U> = T extends U ? T : never

示例

type A = number | string | boolean
type B = number | boolean

type Foo = Extract<A, B>
// 相当于
type Foo = number | boolean
Pick

Pick<T, K extends keyof T> 的作用是将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型。

定义:

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

示例:

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = Pick<Todo, "title" | "completed">;

const todo: TodoPreview = {
  title: "Learn TS",
  completed: false
};
Omit

忽略某个类型中的属性

定义

type Omit = Pick<T, Exclude<keyof T, K>>

示例

interface User {
  id: number;
  age: number;
  name: string;
}

// 相当于: type OmitUser = { age: number; name: string; }
type OmitUser = Omit<User, 'id'>;
Readonly

生成一个新类型,T 中的 K 属性是只读的,K 属性是不可修改的

定义

type Readonly <T> = {
  [P in keyof T ]-?: T[p]
}

示例

interface Foo {
    name: string
    age: number
}
type Bar = Readonly<Foo>
// 相当于
type Bar = {
    readonly name: string
    readonly age: string
}
ReturnType

ReturnType 的作用是用于获取函数 T 的返回类型。

定义

// node_modules/typescript/lib/lib.es5.d.ts

/**
 * Obtain the return type of a function type
 */
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

示例

type T0 = ReturnType<() => string>; // string
type T1 = ReturnType<(s: string) => void>; // void
type T2 = ReturnType<<T>() => T>; // {}
type T3 = ReturnType<<T extends U, U extends number[]>() => T>; // number[]
type T4 = ReturnType<any>; // any
type T5 = ReturnType<never>; // any
type T6 = ReturnType<string>; // Error
type T7 = ReturnType<Function>; // Error
infer

参考文章: