extends 关键字在TS中的两种用法,即接口继承和条件判断。
interface T1 {
name: string
}
interface T2 {
sex: number
}
// 多重继承,逗号隔开
interface T3 extends T1,T2 {
age: number
}
// 合法
const t3: T3 = {
name: 'xiaoming',
sex: 1,
age: 18
}
T1和T2两个接口,分别定义了name属性和sex属性,T3则使用extends使用多重继承的方式,继承了T1和T2,同时定义了自己的属性age,此时T3除了自己的属性外,还同时拥有了来自T1和T2的属性。
// 示例1
interface Animal {
eat(): void
}
interface Dog extends Animal {
bite(): void
}
// A的类型为string
type A = Dog extends Animal ? string : number
const a: A = 'this is string'
Extends 用来条件判断的语法和 JS 的三元表达是很相似,如果问号前面的判断为真,则将第一个类型 string 赋值给 A,否则将第二个类型 number 赋值给 A。
extends判断条件真假的逻辑是什么?
如果 extends 前面的类型能够赋值给 extends 后面的类型,那么表达式判断为真,否则为假。
TypeScript: Documentation - Conditional Types
type A1 = 'x' extends 'x' ? string : number; // string
type A2 = 'x' | 'y' extends 'x' ? string : number; // number
type P<T> = T extends 'x' ? string : number;
type A3 = P<'x' | 'y'> // string | number
!!!注意!!! :
When conditional types act on a generic type, they become *distributive* when given a union type
对于使用extends关键字的条件类型(即上面的三元表达式类型),如果extends前面的参数是一个泛型类型,当传入该参数的是联合类型,则使用分配律计算最终的结果。分配律是指,将联合类型的联合项拆成单项,分别代入条件类型,然后将每个单项代入得到的结果再联合起来,得到最终的判断结果。 所以上面的 A3 是 string | number
满足两个要点即可适用分配律:第一,参数是泛型类型,第二,代入参数的是联合类型
特别要注意的就是 never
// never是所有类型的子类型
type A1 = never extends 'x' ? string : number; // string
type P<T> = T extends 'x' ? string : number;
type A2 = P<never> // never
/*
A2 和 A1 的结果竟然不一样,看起来 never 并不是一个联合类型,所以直接代入条件类型的定义即可,获取的结果应该和A1一直才对啊?
实际上,这里还是条件分配类型在起作用。never 被认为是空的联合类型,也就是说,没有联合项的联合类型,所以还是满足上面的分配律,然而因为没有联合项可以分配,所以 P 的表达式其实根本就没有执行,所以 A2 的定义也就类似于永远没有返回的函数一样,是never类型的。
*/
type P<T> = [T] extends ['x'] ? string : number;
type A1 = P<'x' | 'y'> // number
type A2 = P<never> // string
// 在条件判断类型的定义中,将泛型参数使用[]括起来,即可阻断条件判断类型的分配,此时,传入参数T的类型将被当做一个整体,不再分配。
keyof
与Object.keys
略有相似,只不过keyof
取interface 的键。
interface Point {
x: number;
y: number;
}
// 相当于:
// type keys = "x" | "y"
type keys = keyof Point;
实现一个函数,这个函数获取到一个对象的 value,参数是这个对象和 key
const object = {
a: 1,
b: 'zhangsan'
}
// 这样实现的弊端,1、 key 没有限定,我可以取 a、b 之外的了,2、返回的值没有限定,直接用 any 了
function get(obj: object, key: string): any {
return obj[key]
}
// 改造
function get<T extends object, U extends keyof T>(obj: T, key: U): T[U] {
return obj[key]
}
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. 这个的解释可以参考下面的链接
TypeScript: Documentation - TypeScript 2.9
Typescript 已实现的,可以通过下面的去类比学习便于理解
type Partial<T> = {
[P in keyof T]?: T[P];
};
type Required<T> = {
[P in keyof T]-?: T[P];
};
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
interface User {
id: number;
age: number;
name: string;
};
// 相当于: type PartialUser = { id?: number; age?: number; name?: string; }
type PartialUser = Partial<User>
// 相当于: type PickUser = { id: number; age: number; }
type PickUser = Pick<User, 'id' | 'age'>
type Exclude<T, U> = T extends U ? never : T;
// 相当于: type A = 'a'
type A = Exclude<'x' | 'a', 'x'>
type A = Exclude<'x' | 'a', 'x' | 'y' | 'z'>
// 与 Exclude 实现刚好相反,Exclude 取差集,而 Extract 取交集
type Extract<T, U> = T extends U ? T : never;
// 相当于: type A = 'x'
type A = Exclude<'x' | 'a', 'x'>
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
/* Omit 讲解
1、 keyof T 获取 T 的 key
2、 Exclude , K 就是我们要忽略的键,Exclude 找到 两个的差集
3、 Pick 获取到的就是我们忽略了 K 后面留下来的
*/
interface User {
id: number;
age: number;
name: string;
};
// 相当于: type PickUser = { age: number; name: string; }
type OmitUser = Omit<User, "id">
typeof 代表取某个值的 type
const a: number = 3
// 相当于: const b: number = 4
const b: typeof a = 4