文章目录
- 一、 基本关键词
- 1.1 keyof 索引查询
- 1.2 Typeof 类型运算符
- 1.3 索引访问
- 1.4 模板字符串类型
- 1.5 条件类型 & extends关键词特性 & infer条件推断
- 1.6 映射类型
- 1.7 类型兼容
- 二、Ts内置类型
- 2.1 Partial实现
- 2.2 Readonly 实现
- 2.3 Pick 实现
- 2.4 Record 实现
- 2.5 Exclude 实现
- 2.6 Extract
- 2.7 Omit原理解析
- 2.8 Parameters
- 2.9 ReturnType
- 三、类型保护
- 3.1 in 关键字
- 3.2 typeof 关键字
- 3.3 instanceof 关键字
- 3.4 自定义类型保护的类型谓词
- 四、关于使用ts的一些建议
- 五、探索
一、 基本关键词
1.1 keyof 索引查询
keyof 用来对对象类型索引遍历,并生成其键的字符串或数字联合类型, 对应任何类型T,keyof T的结果为该类型上所有公有属性key的联合
type Point = { x: number; y: number };
type P = keyof Point;
// p = 'x' | 'y'
type Arrayish = { [n: number]: unknown };
type A = keyof Arrayish;
type Mapish = { [k: string]: boolean };
type M = keyof Mapish; // ?
type test = keyof any; // ?
interface Eg1 {
name: string,
readonly age: number,
}
type T1 = keyof Eg1 // ?
class Eg2 {
private name: string;
public readonly age: number;
protected home: string;
}
type T2 = keyof Eg2 // ?
- 在 TypeScript 中支持两种索引签名,数字索引和字符串索引:为了同时支持两种索引类型,就得要求数字索引的返回值必须是字符串索引返回值的子类。其中的原因就是当使用数值索引时,JavaScript 在执行索引操作时,会先把数值索引先转换为字符串索引。
- T2实则被约束为 age,而name和home不是公有属性,所以不能被keyof获取到。
1.2 Typeof 类型运算符
typeof运算符可以在_类型_上下文中使用它来引用变量或属性的_类型:_
let s = "hello";
let n: typeof s;
这对于基本类型不是很有用,但结合其他类型运算符,您可以typeof方便地表达许多模式。例如,让我们从查看预定义类型开始ReturnType。它接受一个_函数类型_并产生它的返回类型:
type Predicate = (x: unknown) => boolean;
type K = ReturnType<Predicate>;
// error
function f() {
return { x: 10, y: 3 };
}
type P = ReturnType<f>;
// succsess
function f() {
return { x: 10, y: 3 };
}
type P = ReturnType<typeof f>;
请记住,值_和_类型_不是一回事。要引用该_值的类型,我们使用:typeof,
注意:TypeScript 有意限制您可以使用的表达式类型typeof,具体来说,仅typeof在标识符(即变量名)或其属性上使用是合法的。这有助于避免编写您认为正在执行但不是的代码的混乱陷阱:
// error
let shouldContinue: typeof f();
1.3 索引访问
interface Eg1 {
name: string,
readonly age: number,
}
type V1 = Eg1['name']
type V2 = Eg1['name' | 'age']
type V2 = Eg1['name' | 'age2222']
type V3 = Eg1[keyof Eg1]
T[keyof T]的方式,可以获取到T所有key的类型组成的联合类型; T[keyof K]的方式,获取到的是T中的key且同时存在于K时的类型组成的联合类型; 注意:如果[]中的key有不存在T中的,则是any;因为ts也不知道该key最终是什么类型,所以是any;且也会报错;
索引类型本身就是一种类型,因此我们可以完全使用联合keyof、 或其他类型:
type Person = { age: number; name: string; alive: boolean };
type I1 = Person["age" | "name"];
type I2 = Person[keyof Person];
type AliveOrName = "alive" | "name";
type I3 = Person[AliveOrName];
使用任意类型进行索引的另一个示例是number用于获取数组元素的类型。我们可以结合它typeof来方便地捕获数组字面量的元素类型:
const MyArray = [
{ name: "Alice", age: 15 },
{ name: "Bob", age: 23 },
{ name: "Eve", age: 38 },
];
type Person = typeof MyArray[number];
type Age = typeof MyArray[number]["age"];
type Age2 = Person["age"];
注意: 只能在索引时使用类型
const MyArray = [
{ name: "Alice", age: 15 },
{ name: "Bob", age: 23 },
{ name: "Eve", age: 38 },
];
type Person = typeof MyArray[number];
// error
const key = "age";
type Age = Person[key];
// succsess
type key = "age";
type Age = Person[key];
1.4 模板字符串类型
模板字符串类型建立在js的模板字符串之上,并且能够通过联合扩展成许多字符串。
type World = "world";
type Greeting = `hello ${World}`;
当在插值位置使用联合时,类型是可以由每个联合成员表示的每个可能的字符串集合:
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
对于模板文字中的每个插值位置,联合是交叉相乘的:
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
type Lang = "en" | "ja" | "pt";
type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`;
- 使用模板文字进行推理(我没看懂)
type PropEventSource<Type> = {
on<Key extends string & keyof Type>
(eventName: `${Key}Changed`, callback: (newValue: Type[Key]) => void ): void;
};
declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>;
const person = makeWatchedObject({
firstName: "Saoirse",
lastName: "Ronan",
age: 26
});
person.on("firstNameChanged", newName => {
// ^?
console.log(`new name is ${newName.toUpperCase()}`);
});
person.on("ageChanged", newAge => {
// ^?
if (newAge < 0) {
console.warn("warning! negative age");
}
})
- 内在字符串操作类型
//将字符串中的每个字符转换为大写版本。
// Uppercase<StringType>
type Greeting = "Hello, world"
type ShoutyGreeting = Uppercase<Greeting>
type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`
type MainID = ASCIICacheKey<"my_app">
// 将字符串中的每个字符转换为等效的小写字母。
// Lowercase<StringType>
type Greeting = "Hello, world"
type QuietGreeting = Lowercase<Greeting>
type ASCIICacheKey<Str extends string> = `id-${Lowercase<Str>}`
type MainID = ASCIICacheKey<"MY_APP">
// 将字符串中的第一个字符转换为等效的大写字母
//Capitalize<StringType>
type LowercaseGreeting = "hello, world";
type Greeting = Capitalize<LowercaseGreeting>;
// 将字符串中的第一个字符转换为等效的小写字母
//Uncapitalize<StringType>
type UppercaseGreeting = "HELLO WORLD";
type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>;
补充案例:去掉字符串前面的空格字符 (没看懂)
type Whitespace = ' ' | '\n' | '\r' | '\t'
type TrimStart<S extends string, P extends string = Whitespace> =
S extends `${P}${infer R}` ? TrimStart<R, P> : S
type String1 = '\t \r \n value'
type Trimmed1 = TrimStart<String1>
type String2 = '---value'
type Trimmed2 = TrimStart<String2, '-'>
1.5 条件类型 & extends关键词特性 & infer条件推断
extends 一般用于接口,表示继承,也可表示条件类型,可用于条件判断
- 用于继承
// 用于继承
interface T1 {
name: string,
}
interface T2 {
sex: number,
}
interface T3 extends T1, T2 {
age: number,
}
// 注意,接口支持多重继承,语法为逗号隔开。
// 如果是type实现继承,则可以使用交叉类型type A = B & C & D。
- 条件判断,如果前面的条件满足,则返回问号后的第一个参数,否则第二个。类似于js的三元运算。
type A1 = 'x' extends 'x' ? 1 : 2;
type A2 = 'x' | 'y' extends 'x' ? 1 : 2;
type P<T> = T extends 'x' ? 1 : 2;
type A3 = P<'x' | 'y'>
提问:为什么A2和A3的值不一样?
- 如果用于简单的条件判断,则是直接判断前面的类型是否可分配给后面的类型
- 若extends前面的类型是泛型,且泛型传入的是联合类型时,则会依次判断该联合类型的所有子类型是否可分配给extends后面的类型(是一个分发的过程)。
总结,extends前面的参数为联合类型时则会分解(依次遍历所有的子类型进行条件判断)联合类型进行判断。然后将最终的结果组成新的联合类型。
如果不想被分解(分发),做法也很简单,可以通过简单的元组类型包裹以下:
type P<T> = [T] extends ['x'] ? 1 : 2;
type A4 = P<'x' | 'y'>
- 条件类型推断 infer
条件类型为我们提供了一种使用infer关键字方法来推断在真实分支中比较的类型,
- infer关键词只能在extends条件类型上使用,不能在其他地方使用。
//实现一个推导数组所有元素的类型:
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
? Return : never;
type Str = GetReturnType<(x: string) => string>;
type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;
type Num = GetReturnType<() => number>;
当从具有多个调用签名的类型(例如重载函数的类型)进行推断时,会根据_最后一个_签名进行推断。无法根据参数类型列表执行重载决议。
declare function stringOrNum(x: string): number;
declare function stringOrNum(x: number): string;
declare function stringOrNum(x: string | number): string | number;
type T1 = ReturnType<typeof stringOrNum>;
当条件类型作用于泛型类型时,它们在给定联合类型时变得可_分配_
type ToArray<Type> = Type extends any ? Type[] : never;
type StrArrOrNumArr = ToArray<string | number>;
通常,分配性是期望的行为。extends为避免这种行为,您可以用方括号将关键字的每一侧括起来。
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
type StrArrOrNumArr = ToArrayNonDist<string | number>;
1.6 映射类型
映射类型建立在索引签名的语法之上,用于声明未提前声明的属性类型
一般我们声明接口:
type OnlyBoolsAndHorses = {
[key: string]: boolean | Horse;
};
const conforms: OnlyBoolsAndHorses = {
del: true,
rodney: false,
};
映射类型是一种通用类型,它使用PropertyKeys 的联合(通常通过 akeyof创建)来遍历键以创建类型:
type OptionsFlags<Type> = {
[Property in keyof Type]: boolean;
};
type FeatureFlags = {
darkMode: () => void;
newUserProfile: () => void;
};
type FeatureOptions = OptionsFlags<FeatureFlags>;
在映射期间可以应用两个额外的修饰符:它们分别影响可变性和可选性。可以通过- 和 + 来改变可变性和可选性
type CreateMutable<Type> = {
-readonly [Property in keyof Type]: Type[Property];
};
type LockedAccount = {
readonly id: string;
readonly name: string;
};
type UnlockedAccount = CreateMutable<LockedAccount>;
type Concrete<Type> = {
[Property in keyof Type]-?: Type[Property];
};
type MaybeUser = {
id: string;
name?: string;
age?: number;
};
type User = Concrete<MaybeUser>;
在 TypeScript 4.1 及更高版本中,您可以使用映射类型中的as子句重新映射映射类型中的键:
type Getters<Type> = {
[Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
};
interface Person {
name: string;
age: number;
location: string;
}
type LazyPerson = Getters<Person>;
可以通过条件类型生成 never 来过滤掉键:
// 移除kind属性
type RemoveKindField<Type> = {
[Property in keyof Type as Exclude<Property, "kind">]: Type[Property]
};
interface Circle {
kind: "circle";
radius: number;
}
type KindlessCircle = RemoveKindField<Circle>;
可以映射任意联合,不仅是的string | number | symbol的联合
type EventConfig<Events extends { kind: string }> = {
[E in Events as E["kind"]]: (event: E) => void;
}
type SquareEvent = { kind: "square", x: number, y: number };
type CircleEvent = { kind: "circle", radius: number };
type Config = EventConfig<SquareEvent | CircleEvent>
1.7 类型兼容
在集合论中,如果一个集合的所有元素在集合B中都存在,则A是B的子集;
类型系统中,如果一个类型的属性更具体,则该类型是子类型。(因为属性更少则说明该类型约束的更宽泛,是父类型)
因此,我们可以得出基本的结论**:子类型比父类型更加具体,父类型比子类型更宽泛。** 下面我们也将基于类型的可复制性(可分配性)、协变、逆变、双向协变等进行进一步的讲解。
- 可赋值性
interface Animal {
name: string;
}
interface Dog extends Animal {
break(): void;
}
let a: Animal;
let b: Dog;
// 可以赋值,子类型更佳具体,可以赋值给更佳宽泛的父类型
a = b;
// 反过来不行
b = a;
- 可赋值性在联合类型中的特性
type A = 1 | 2 | 3;
type B = 2 | 3;
let a: A;
let b: B;
// 不可赋值
b = a;
// 可以赋值
a = b;
是不是A的类型更多,A就是子类型呢?恰恰相反,A此处类型更多但是其表达的类型更宽泛,所以A是父类型,B是子类型。因此b = a不成立(父类型不能赋值给子类型),而a = b成立(子类型可以赋值给父类型)。
- 协变
interface Animal {
name: string;
}
interface Dog extends Animal {
break(): void;
}
let Eg1: Animal;
let Eg2: Dog;
// 兼容,可以赋值
Eg1 = Eg2;
let Eg3: Array<Animal>
let Eg4: Array<Dog>
// 兼容,可以赋值
Eg3 = Eg4
通过Eg3和Eg4来看,在Animal和Dog在变成数组后,Array依旧可以赋值给Array,因此对于type MakeArray = Array来说就是协变的。
最后引用维基百科中的定义:
协变与逆变(Covariance and contravariance )是在计算机科学中,描述具有父/子型别关系的多个型别通过型别构造器、构造出的多个复杂型别之间是否有父/子型别关系的用语。
简单说就是,具有父子关系的多个类型,在通过某种构造关系构造成的新的类型,如果还具有父子关系则是协变的,而关系逆转了(子变父,父变子)就是逆变的。可能听起来有些抽象,下面我们将用更具体的例子进行演示说明:
- 逆变
interface Animal {
name: string;
}
interface Dog extends Animal {
break(): void;
}
type AnimalFn = (arg: Animal) => void
type DogFn = (arg: Dog) => void
let Eg1: AnimalFn;
let Eg2: DogFn;
// 不再可以赋值了,
// AnimalFn = DogFn不可以赋值了, Animal = Dog是可以的
Eg1 = Eg2;
// 反过来可以
Eg2 = Eg1;
理论上,Animal = Dog是类型安全的,那么AnimalFn = DogFn也应该类型安全才对,为什么Ts认为不安全呢?看下面的例子:
let animal: AnimalFn = (arg: Animal) => {}
let dog: DogFn = (arg: Dog) => {
arg.break();
}
// 假设类型安全可以赋值
animal = dog;
// 那么animal在调用时约束的参数,缺少dog所需的参数,此时会导致错误
animal({name: 'cat'});
从这个例子看到,如果dog函数赋值给animal函数,那么animal函数在调用时,约束的是参数必须要为Animal类型(而不是Dog),但是animal实际为dog的调用,此时就会出现错误。
因此,Animal和Dog在进行type Fn = (arg: T) => void构造器构造后,父子关系逆转了,此时成为“逆变”。
- 双向协变
Ts在函数参数的比较中实际上默认采取的策略是双向协变:只有当源函数参数能够赋值给目标函数或者反过来时才能赋值成功。
这是不稳定的,因为调用者可能传入了一个具有更精确类型信息的函数,但是调用这个传入的函数的时候却使用了不是那么精确的类型信息(典型的就是上述的逆变)。 但是实际上,这极少会发生错误,并且能够实现很多JavaScript里的常见模式:
// lib.dom.d.ts中EventListener的接口定义
interface EventListener {
(evt: Event): void;
}
// 简化后的Event
interface Event {
readonly target: EventTarget | null;
preventDefault(): void;
}
// 简化合并后的MouseEvent
interface MouseEvent extends Event {
readonly x: number;
readonly y: number;
}
// 简化后的Window接口
interface Window {
// 简化后的addEventListener
addEventListener(type: string, listener: EventListener)
}
// 日常使用
window.addEventListener('click', (e: Event) => {});
window.addEventListener('mouseover', (e: MouseEvent) => {});
- infer推导的名称相同并且都处于逆变的位置,则推导的结果将会是交叉类型。
type Bar<T> = T extends {
a: (x: infer U) => void;
b: (x: infer U) => void;
} ? U : never;
// type T1 = string
type T1 = Bar<{ a: (x: string) => void; b: (x: string) => void }>;
// type T2 = never
type T2 = Bar<{ a: (x: string) => void; b: (x: number) => void }>;
- infer推导的名称相同并且都处于协变的位置,则推导的结果将会是联合类型。
type Foo<T> = T extends {
a: infer U;
b: infer U;
} ? U : never;
// type T1 = string
type T1 = Foo<{ a: string; b: string }>;
// type T2 = string | number
type T2 = Foo<{ a: string; b: number }>;
二、Ts内置类型
2.1 Partial实现
/**
* 核心实现就是通过映射类型遍历T上所有的属性,
* 然后将每个属性设置为可选属性
*/
type Partial<T> = {
[P in keyof T]?: T[P];
}
// 扩展一下
type PartialOptional<T, K extends keyof T> = {
[P in K]?: T[P];
}
type Eg1 = PartialOptional<{
key1: string,
key2: number,
key3: ''
}, 'key1' | 'key2'>;
解析:
- [P in keyof T]通过映射类型,遍历T上的所有属性
- ?:设置为属性为可选的
- T[P]设置类型为原来的类型
2.2 Readonly 实现
/**
* 主要实现是通过映射遍历所有key,
* 然后给每个key增加一个readonly修饰符
*/
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}
type Eg = Readonly<{
key1: string,
key2: number,
}>
2.3 Pick 实现
挑选一组属性并组成一个新的类型。
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
2.4 Record 实现
/**
* 核心实现就是遍历K,将值设置为T
*/
type Record<K extends keyof any, T> = {
[P in K]: T
}
/**
* @example
* type Eg2 = {a: B, b: B}
*/
interface A {
a: string,
b: number,
}
interface B {
key1: number,
key2: string,
}
type Eg2 = Record<keyof A, B>
- Partial、Readonly和Pick都属于同态的,即其实现需要输入类型T来拷贝属性,因此属性修饰符(例如readonly、?:)都会被拷贝。
- Record是非同态的,不需要拷贝属性,因此不会拷贝属性修饰符
可以看到Pick的实现中,注意P in K(本质是P in keyof T),T为输入的类型,而keyof T则遍历了输入类型;而Record的实现中,并没有遍历所有输入的类型,K只是约束为keyof any的子类型即可。
最后再类比一下Pick、Partial、readonly这几个类型工具,无一例外,都是使用到了keyof T来辅助拷贝传入类型的属性。
2.5 Exclude 实现
Exclude<T, U>提取存在于T,但不存在于U的类型组成的联合类型。
/**
* 遍历T中的所有子类型,如果该子类型约束于U(存在于U、兼容于U),
* 则返回never类型,否则返回该子类型
*/
type Exclude<T, U> = T extends U ? never : T;
/**
* @example
* type Eg = 'key1'
*/
type Eg = Exclude<'key1' | 'key2', 'key2'>
- never表示一个不存在的类型
- never与其他类型的联合后,是没有never的
- 因此上述Eg其实就等于key1 | never,也就是type Eg = key1
type Eg2 = string | number | never
2.6 Extract
Extract<T, U>提取联合类型T和联合类型U的所有交集。
type Extract<T, U> = T extends U ? T : never;
type Eg = Extract<'key1' | 'key2', 'key1'>
2.7 Omit原理解析
Omit<T, K>从类型T中剔除K中的所有属性。
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
// 另外一种实现
type Omit2<T, K extends keyof any> = {
[P in Exclude<keyof T, K>]: T[P]
}
2.8 Parameters
Parameters 获取函数的参数类型,将每个参数类型放在一个元组中。
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
type Eg = Parameters<(arg1: string, arg2: number) => void>;
- Parameters首先约束参数T必须是个函数类型,所以(…args: any) => any>替换成Function也是可以的
- 具体实现就是,判断T是否是函数类型,如果是则使用inter P让ts自己推导出函数的参数类型,并将推导的结果存到类型P上,否则就返回never;
- type Eg = [arg1: string, arg2: number]这是一个元组,但是和我们常见的元组type tuple = [string, number]。官网未提到该部分文档说明,其实可以把这个作为类似命名元组,或者具名元组的意思去理解。实质上没有什么特殊的作用,比如无法通过这个具名去取值不行的。但是从语义化的角度,个人觉得多了语义化的表达罢了。
- 定义元祖的可选项,只能是最后的选项
/**
* 普通方式
*/
type Tuple1 = [string, number?];
const a: Tuple1 = ['aa', 11];
const a2: Tuple1 = ['aa'];
/**
* 具名方式
*/
type Tuple2 = [name: string, age?: number];
const b: Tuple2 = ['aa', 11];
const b2: Tuple2 = ['aa'];
2.9 ReturnType
获取函数的返回值类型。
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
三、类型保护
类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。 换句话说,类型保护可以保证一个字符串是一个字符串,尽管它的值也可以是一个数值。类型保护与特性检测并不是完全不同,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。目前主要有四种的方式来实现类型保护:
3.1 in 关键字
interface Admin {
name: string;
privileges: string[];
}
interface Employee {
name: string;
startDate: Date;
}
type UnknownEmployee = Employee | Admin;
function printEmployeeInformation(emp: UnknownEmployee) {
console.log("Name: " + );
if ("privileges" in emp) {
console.log("Privileges: " + emp.privileges);
}
if ("startDate" in emp) {
console.log("Start Date: " + emp.startDate);
}
}
3.2 typeof 关键字
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
typeof 类型保护只支持两种形式:typeof v === “typename” 和 typeof v !== typename,“typename” 必须是 “number”, “string”, “boolean” 或 “symbol”。 但是 TypeScript 并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。
3.3 instanceof 关键字
interface Padder {
getPaddingString(): string;
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) {}
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}
class StringPadder implements Padder {
constructor(private value: string) {}
getPaddingString() {
return this.value;
}
}
let padder: Padder = new SpaceRepeatingPadder(6);
if (padder instanceof SpaceRepeatingPadder) {
// padder的类型收窄为 'SpaceRepeatingPadder'
}
3.4 自定义类型保护的类型谓词
function isNumber(x: any): x is number {
return typeof x === "number";
}
function isString(x: any): x is string {
return typeof x === "string";
}
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
四、关于使用ts的一些建议
- 使用模块代替命名空间 (命名文件里使用了export字段后文件被认为模块文件,无法使用命名空间)
- 不要使用/// 命令
- 类型命名使用大驼峰
- 使用import引入类型时,使用type 字段
- 使用Record代替object 类型
五、探索
映射类型与此类型操作部分中的其他功能很好地配合:(我没看懂)
type ExtractPII<Type> = {
[Property in keyof Type]: Type[Property] extends { pii: true } ? true : false;
};
type DBFields = {
id: { format: "incrementing" };
name: { type: string; pii: true };
};
type ObjectsNeedingGDPRDeletion = ExtractPII<DBFields>;
获取T中所有类型为函数的key组成的联合类型。
/**
* @desc NonUndefined判断T是否为undefined
*/
type NonUndefined<T> = T extends undefined ? never : T;
/**
* @desc 核心实现
*/
type FunctionKeys<T extends object> = {
[K in keyof T]: NonUndefined<T[K]> extends Function ? K : never;
}[keyof T];
/**
* @example
* type Eg = 'key2' | 'key3';
*/
type AType = {
key1: string,
key2: () => void,
key3: Function,
};
type Eg = FunctionKeys<AType>;
- 首先约束参数T类型为object
- 通过映射类型K in keyof T遍历所有的key,先通过NonUndefined<T[K]>过滤T[K]为undefined | null的类型,不符合的返回never
- 若T[K]为有效类型,则判断是否为Function类型,是的话返回K,否则never;此时可以得到的类型,例如:
- 最后经过{省略}[keyof T]索引访问,取到的为值类型的联合类型never | key2 | key3,计算后就是key2 | key3;
enum drawEnum {
residualPlots = '残差图',
scalar = '标量图',
streamtrace = '流线图',
contour = '等值线图',
linearGraph = '曲线图'
}
export type drawType = keyof typeof drawEnum;
export type drawText = `${drawEnum}`;