索引类型
let adObj = {
a: 1,
b: 2,
c: 3,
};
// 获取对象中的指定属性的值集合
function getValues(adObj: any, keys: string[]) {
return keys.map((key) => adObj[key]);
}
console.log(getValues(adObj, ["a", "b"])); // [1, 2]
console.log(getValues(adObj, ["e", "f"])); // [undefined, undefined]
虽然obj中并不包含e, f属性,但TS编译器并未报错 此时使用TS索引类型,对这种模式做类型约束
概念
索引类型的查询操作符
keyof T :表示类型T的所有公共属性的字面量的联合类型
interface obj {
a: number;
b: string;
}
let key: keyof obj;
索引访问操作符
T[K] :表示对象T的属性K所代表的类型
interface obj {
a: number;
b: string;
}
let value: obj["a"];
泛型约束
T extends U :表示泛型变量可以通过继承某个类型,获得某些属性
索引类型
约束目的:从 obj 对象中抽取的属性数组 keys 中的元素,一定得是 obj 对象中的属性 思路:
- 定义泛型变量T, K,分别约束obj对象和keys数组
- 为K增加一个泛型约束,使K继承Obj的所有属性的联合类型, 即
K extends keyof T
- 此时,函数返回值类型-数组的元素类型,就是属性K对应的数组,即
T[K][]
let adObj = {
a: 1,
b: 2,
c: 3,
};
function getValues<T, K extends keyof T>(adObj: T, keys: K[]): T[K][] {
return keys.map((key) => adObj[key]);
}
console.log(getValues(adObj, ["a", "b"])); // [1, 2]
console.log(getValues(adObj, ["e", "f"]));
索引类型可以约束对象属性的查询和访问 配合泛型约束能够建立对象,对象属性,属性值之间的约束关系
映射类型
TS允许将一个类型映射成另外一个类型
// 定义接口 Obj
interface Obj {
a: string;
b: number;
c: boolean;
}
// 使用类型别名定义类型ReadonlyObj
type ReadonlyObj = Readonly<Obj>; // Readonly是TS内置的泛型接口
ReadonlyObj与Obj成员完全相同,区别是ReadonlyObj中的成员属性均为只读
Readonly的实现原理
Readonly是一个可索引类型的泛型接口
- 索引签名为P in keyof T :
其中 keyof T就是一个一个索引类型的查询操作符,表示类型T所有属性的联合类型
- P in :
相当于执行了一个 for in 操作,会把变量P依次绑定到T的所有属性上
- 索引签名的返回值就是一个索引访问操作符 : T[P] 这里代表属性P所指定的类型
- 最后再加上Readonly就把所有的属性变成了只读,这就是 Readonly 的实现原理
其他的映射类型
可选和只读映射类型的实现几乎一样, 只读属性变为可选
type PartialObj = Partial<Obj>; // 可选
抽取接口Obj中的属性a和b,形成新类型
type PickObj = Pick<Obj, "a" | "b">;
Pick映射类型的实现原理
Pick映射类型有两个参数: 第一个参数T,表示要抽取的目标对象 第二个参数K,具有一个约束:K一定要来自T所有属性字面量的联合类型,即映射得到的新类型的属性一定要从K中选取 以上三种映射类型官方称为同态,意思是只作用于obj属性而不会引入新的属性
引入新属性的非同态映射类型
第一个参数是预定义的新属性,如x,y 第二个参数就是已知类型
type RecordObj = Record<"x" | "y", Obj>;
映射出的新类型所具有的属性由Record的第一个属性指定,而这些属性类型为第二个参数指定的已知类型,这种类型就是一个非同态的类型
映射类型本质上是一种预先定义的泛型接口,通常还会结合索引类型,获取对象的属性和属性值,从而将一个对象映射为我们想要的结构
条件类型
条件类型是一种由条件表达式所决定的类型 条件类型使类型具有了不唯一性,同样增加了语言的灵活性
声明: T extends U ? X : Y 若类型T可被赋值给类型U,那么结果类型就是X类型,否则就是Y类型
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "Function" : "object";
type T1 = TypeName<string>;
type T2 = TypeName<string[]>;
分步式条件类型
当类型T为联合类型时: T为类型A和类型B的联合类型,结果类型会变成多个条件类型的联合类型 (A | B) extends U ? X : Y
可以将A和B进行拆解: (A extends U ? X : Y) | (B extends U ? X : Y)
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "Function" : "object";
type T3 = TypeName<string | string[]>;
分步式条件类型的应用
// 如果T可以被赋值给U,结果类型为never类型,否则为T类型
type Diff<T, U> = T extends U ? never : T;
type T4 = Diff<"a" | "b" | "c", "a" | "e">;
// 拆解分析:
// Diff<"a", "a" | "e"> | Diff<"b", "a" | "e"> | Diff<"c", "a" | "e">
// never | "b" | "c"
// "b" | "c"
先判断a是否可以被赋值给这个字面量联合类型’a’ | ‘e’,答案是可以的,所以返回never 继续,因为b不可以被赋值给字面量联合类型’a’ | ‘e’,所以返回b 继续,c不可以被赋值给’a’ | ‘e’,所以返回c 最后,never和b,c的联合类型为’b’ | ‘c’
Diff类型作用:可以从类型T中过滤掉可以被赋值给类型U的类型
也可以实现从类型T中移除不需要的类型,如undefined和null
定义一个NotNull,从T中过滤掉undefined和null
// Diff扩展:从T中过滤掉undefined和null
type NotNull<T> = Diff<T, undefined | null>;
// 过滤掉undefined和null,T5的类型就变成了string和number
type T5 = NotNull<string | number | undefined | null>;
// Diff 的内置类型叫做 Exclude<T, U>
// NotNull 的内置类型叫做NonNullable<T>
// Extract<T, u>
// Extract 和 Exclude 相反
// Exclude 作用是从类型T中过滤掉可以赋值给类型U的类型
// Extract 作用是可以从类型T中抽取出可以赋值给U的类型
type T6 = Extract<"a" | "b" | "c", "a" | "e">;
type T7 = Exclude<"a" | "b" | "c", "a" | "e">;
// ReturnType<T>:可获取函数返回值类型
type T8 = ReturnType<() => string>;
// 源码
/**
* Obtain the return type of a function type
*/
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
// 分析
// T extends (...args: any) => any:
// ReturnType要求参数T可以赋值给一个函数,这个函数有任意的参数,返回值类型也是任意的
// 由于函数返回值类型不确定,这里使用了infer关键字,表示待推断,延迟推断,需要根据实际的情况确定
// infer R ? R : any:
// 如果实际类型是R,那么结果类型就是R,否则返回值类型就是any