Skip to content

泛型

泛型介绍

场景:参数可以是任意值,返回值将参数原样返回

  1. 输出类型和输入类型关联

  2. 两个关联的输入类型

常规写法

tsx
const identity = (arg) => arg;

type idBoolean = (arg: boolean) => boolean;
type idNumber = (arg: number) => number;
type idString = (arg: string) => string;
...
js
identity("string").length; // ok
identity("string").toFixed(2); // ok
identity(null).toString(); // ok
...

使用泛型重构代码,新建泛型变量 T,分别在参数和返回值中占位

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

T 是一个抽象类型,只有在调用时才确定值。

例子:定义多个变量

tsx
function identity<T, U>(value: T, message: U): T {
  console.log(message)
  return value
}
// 1.显示设定类型
console.log(identity<Number, string>(68, 'Semlinker'))
// 2.编译器自动推导类型
console.log(identity(68, 'Semlinker'))

泛型约束

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

问题:T 可以是任何类型,不管使用什么属性都会报错(除非这个属性和方法是所有集合共有)

思路:参数类型包含 size 属性即可,可通过 extends

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

泛型工具类型

typeof

typeof:获取变量或属性的类型

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

也可获取函数对象类型

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

keyof

keyof:获取某种类型的所有键名,返回类型是联合类型

tsx
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

typescript 支持 两种索引签名,数字索引、字符串索引

为了同时支持 两种索引类型,数字索引的返回值,必须是字符串索引返回值的子类。

原因:使用数字索引,会先被 JavaScript 转换为 字符串索引进行操作。

tsx
interface StringArray {
  // 字符串索引 -> keyof StringArray => string | number
  [index: string]: string
}

interface StringArray1 {
  // 数字索引 -> keyof StringArray1 => number
  [index: number]: string
}

keyof 作用

限制属性名的范围

tsx
function prop<T extends object, K extends keyof T>(obj: T, key: K) {
  return obj[key]
}

T extends object:T 约束该类型为 object 子类型

K extends keyof T:K 约束键名为 T 键名子类型

访问已存在的属性

js
type Todo = {
  id: number;
  text: string;
  done: boolean;
}

const todo: Todo = {
  id: 1,
  text: "Learn TypeScript keyof",
  done: false
}

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

const id = prop(todo, "id"); // const id: number
const text = prop(todo, "text"); // const text: string
const done = prop(todo, "done"); // const done: boolean

注意:会阻止访问不存在的属性

js
const date = prop(todo, "date");
Argument of type '"date"' is not assignable to parameter of type '"id" | "text" | "done"'.

in

in 用来遍历枚举类型

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

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

infer

infer:条件判断中,类型推导

关键:看 infer 放置位置,在哪就推断哪个值

格式:使用 infer 声明一个变量类型,条件类型判断为 true 时生效

js
type ExtractSelf<T> = T extends (infer U) ? U : T;

type T1 = ExtractSelf<string>;        // string
type T2 = ExtractSelf<() => void>;    // () => void
type T3 = ExtractSelf<Date[]>;        // Date[]
type T4 = ExtractSelf<{ a: string }>; // { a: string }

ExtractArrayItemType:推断元素类型。普通元素直接返回类型,数组元素返回数组中元素类型

js
type ExtractArrayItemType<T> = T extends (infer U)[] ? U : T;

// 条件判断都为 false,返回 T
type T1 = ExtractArrayItemType<string>;         // string
type T2 = ExtractArrayItemType<() => number>;   // () => number
type T4 = ExtractArrayItemType<{ a: string }>;  // { a: string }

// 条件判断为 true,返回 U
type T3 = ExtractArrayItemType<Date[]>;     // Date

(infer U)[] 可被分配为 Date[],U 所在的位置是 Date,返回 Date

根据优先级,此处 infer U 需加上 ()

ExtractReturnType:推断函数返回值

js
type ExtractReturnType<T> = T extends () => (infer U) ? U : T;

// 条件判断为 true,返回 U
type T1 = ExtractReturnType<() => number>;   // number。

ExtractAllType:推断出联合类型

js
type ExtractAllType<T> = T extends { x: infer U, y: infer U } ? U : T;

type T1 = ExtractAllType<{ x: string, y: number }>; // string | number

// 任意类型 推断
type ExtractAllType<T> = T extends { [k: string]: infer U } ? U : T;

type T1 = ExtractAllType<{ x: string, y: number, z: boolean }>; // string | number | boolean

ExtractArrayItemType:元组类型转为联合类型

js
type ExtractArrayItemType<T> = T extends (infer U)[] ? U : T;

type ItemTypes = ExtractArrayItemType<[string, number]>; // string | number

extends

extends 添加泛型约束

js
interface Lengthwise {
  length: number;
}

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

泛型函数被定义了约束。此时传入的值,必须要包含 length 属性

js
loggingIdentity(3) // Error, number doesn't have a .length property
loggingIdentity({ length: 10, value: 3 }) // ok

场景:获取 T 对应 key 的类型

tsx
type MessageOf<T> = T['message']
// Type '"message"' cannot be used to index type 'T'.

T 没有 message 属性,需要通过 extends 进行约束

tsx
type MessageOf<T extends { message: unknown }> = T['message']

设置一个默认类型 never

tsx
type MessageOf<T> = T extends { message: unknown } ? T['message'] : never

interface Email {
  message: string
}

interface Dog {
  bark(): void
}

type EmailMessageContents = MessageOf<Email> // string

type DogMessageContents = MessageOf<Dog> // never

场景:如果是数组,获取值类型,反之原样返回

tsx
type Flatten<T> = T extends any[] ? T[number] : T //

// Extracts out the element type.
type Str = Flatten<string[]> // string

// Leaves the type alone.
type Num = Flatten<number> // number

T[number]:获取数组值

相关链接

[-] 泛型使用用例

[-] infer 易懂

[-] extends 易懂

[-] 条件类型