extends有三种用途:继承、泛型约束、分配(条件类型)
一方面和 js 作用一致,用在 class
上。
另一方面,也可以继承类型
interface Person { name: string }
interface Child extends Person { age: number }
// interface Child = { name: string; age: number }
在泛型中,有时需要约束类型参数,可以借助extends
来完成,比如
// 希望传入的参数是个函数
type Type<T extends (props: any) => void>= { info: T };
const obj: Type<() => void> = {info: function(){}}
// obj中的person属性必须包含name属性
interface Type<T extends { name: string }> {
person: T;
}
const obj: Type<{ name: string; age: number }> = {
person: { name: 'hxy', age: 25 },
};
extends
还可以用来判断一个类型是否可以分配给另一个类型,先看例子
type A = { name: string };
type B = { name: string };
type C = Child extends Person ? string : number;
// type C = string
和三元运算一样,第三行的意思是,A类型 是否可以分配给 B类型?是的话 C 为string
,否则 C 为number
。这里的 A 和 B 类型一致,当然可以分配。
这里就可以明白上面第二条的泛型约束 T1 extends T2
,意思就是 T1 需要可以分配给 T2。
需要强调的是,**A 可以分配给 B,不等于 A 是 B 的子集。**再看例子
type A = { name: string; age: number };
type B = { name: string };
type C = A extends B ? string : number;
// type C = string
这里的 A 显然不是 B 的子集,但条件判断依然成立(依然可以分配),可以理解为如果 A 是在 B 上扩展了一些属性,比如class A extends B {}
,那么 A 就可以分配给 B。如果这里把 A 的 name 属性变为可选属性,那当然就不可分配。
有了 extends
可以用作分配的基础知识,下面来详细看看[条件类型](# 条件类型)
从[上文](#3. 分配(条件类型) )明白条件类型的基本用法,下面看一个搭配泛型使用的例子
interface IdLabel {
id: number;
}
interface NameLabel {
name: string;
}
function createLabel(id: number): IdLabel;
function createLabel(name: string): NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
throw "unimplemented";
}
这里是函数重载的写法,描述了 createLabel
是如何基于输入值的类型不同而做出不同的决策,返回不同的类型。可以看到这里只有两个类型,就需要重载三次,如果有一天要加一个类型,重载的次数会呈指数增加。
写js代码时我们通常会使用三元运算符减少一些if/switch
语句,上面的例子同样可以用条件类型优化
type NameOrId<T extends number | string> = T extends number
? IdLabel
: NameLabel;
此时可以通过这个条件类型简化函数重载
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
throw "unimplemented";
}
let a = createLabel("typescript");
// let a: NameLabel
let b = createLabel(2.8);
// let b: IdLabel
let c = createLabel(Math.random() ? "hello" : 42);
// let c: NameLabel | IdLabel
type Example1 = 'x' extends 'x' ? string[] : number[];
// type Example1 = string[]
type Example2 = 'x' | 'y' extends 'x' ? string[] : number[];
//type Example2 = number[]
type ToArray<T> = T extends 'x' ? string[] : number[];
type Example3 = ToArray<'x' | 'y'>; // ?
直觉告诉我Example3
是number[]
,实际上是string[] | number[]
,因为若给 ToArray
传入一个联合类型,这个条件类型会被分发到联合类型的每个成员
ToArray<'x' | 'y'>;
// 相当于
ToArray<'x'> | ToArray<'y'>;
// 结果显然就是 string[] | number[]
通常这是我们期望的行为,如果要避免这种行为,可以用方括号包裹 extends
关键字的每一部分。
type ToArray<T> = [T] extends ['x'] ? string[] : number[];
type Example3 = ToArray<'x' | 'y'>;
// type Example3 = number[];
条件类型的基本语法 T extends U ? X : Y;
如果占位符类型 U
是一个可以被分解成几个部分的类型,比如数组类型、元组类型、函数类型、字符串字面量类型等。这时候可以通过 infer
来推断 U
类型中某个部分的类型。
infer
使用限制:只能被用在条件类型中,只能在true分支使用。
// 1. 推断数组(或者元组)的类型
type ArrayItem<T> = T extends (infer U)[] ? U : never;
type Example1 = Type1<string[]>
// type Example1 = string
type Example2 = ArrayItem<[number, string]>
// type Example2 = string | number
// 2. 推断数组(或者元组)第一个元素的类型(最后一个写法类似)
type First<T extends unknown[]> = T extends [infer P, ...infer _] ? P : never
type Example3 = First<[3, 2, 1]>
// type Example3 = 3
// 3. 推断函数类型的参数 和 推断函数类型的返回值,内置了Parameters 和 ReturnType
可以在数组、元祖、接口、对象类型上使用readonly。
类里的字段加一个 readonly 前缀修饰符,会阻止在构造函数之外的赋值。
as const也会把字段转为只读,一般用在数组和对象上。
type T1 = readonly [number, string] // 元祖
type T2 = readonly string[] // 数组
type T3 = {readonly name: string}
type T4 = readonly string // ❌ 不允许这样写
class Person {
readonly name: string = "hxy";
// 只允许在constructor里修改
constructor(otherName?: string) {
if (otherName !== undefined) {
this.name = otherName;
}
}
}
const arr = [1,2] as const
// const arr: readonly [1, 2]
const arr = {name: 'hxy'} as const
// const arr: { readonly name: "hxy"; }
将 T 中所有属性变为可选的
type Partial<T> = { [P in keyof T]?: T[P] };
将 T 中所有属性变为只读的
type Readonly<T> = { readonly [P in keyof T]: T[P] };
从已有对象类型中选取给定的属性及其类型,然后构建一个新的对象类型。
T 表示源对象类型,类型参数 K 提供了待选取的属性名类型,它必须为类型 T 中存在的属性。
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
// 例
type T1 = { name: string; age: number; gender: string };
type T2 = Pick<T1, 'age'|'name'>
// type T1 = { age: number; name: string; }
构建一个新的对象类型,键的类型是 K (in K),值的类型是 T
type Record<K extends keyof any, T> = { [P in K]: T };
// 例
type keys = 'A' | 'B'
type T = Record<keys, number>
// type T = { A: number; B: number; }
获取函数的返回类型,借助条件类型和infer
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
// 例
const add = (num1: number, num2: number) => num1 + num2;
type returnType = ReturnType<typeof add>;
// type returnType = number
获取函数的参数的类型,返回的一定是元祖或数组类型。借助条件类型和infer
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
// 例
const add = (...args: number[]) => args.reduce((p, c) => p + c);
type Para = Parameters<typeof add>;
// type Para = number[]
const a: Para = [1, 2];