- 目录TOC
- 集合论是TS类型的一个理论基础
- 每个类型都代表一类有相同特征的值
- 例如
number
类型是一个无限的集合,代表所有的数字string
和object
同理 - 除了无限集合还有一些有限集合,例如
boolean
/null
/undefined
/字符串常量/字符串常量联合类型都是有限集合,它们只包含有限个元素
- 例如
// both true, string literal ⊂ string
type W = 'a' extends string ? true : false;
type X = 'a' | 'b' extends string ? true : false;
// true, string literal ⊆ same string literal
type Y = 'a' extends 'a' ? true : false;
// true, string ⊆ string
type Z = string extends string ? true : false;
unknown
/any
/never
unknown
是top type
,类似集合中的全集U
,包含所有可能值- 代表一个暂时未知的类型,它是在类型系统中明确定义的
- 可以把任意值赋给
unknown
- 但不能把
unknown
直接赋值给其它类型(unknown/any 除外)- 需要先通过
typeof
或type assert
确定类型再赋值
- 需要先通过
any
代表这是一个任意值,更像一个 ts 指令,告诉 ts 禁用类型系统- 可以赋值给任意类型
- 也可以被任意类型值赋值
never
是一个bottom type
,类似集合中的空集- 它只能接受
never
(any/unknown 都无法赋值给它)
- 它只能接受
let vAny: any = 'any1'
let vAny2:any = 'any2'
let vUnknown:unknown = 'unknown1'
let vUnknown2:unknown='unknown2'
let n1:number = 1
let s1:string ='hello'
/** never 无法被赋值,但它能赋值给其它任意类型 */
let nv:never = 'never' // error
/** 任意值(包括 unknown和 any 自身)都可以赋值给any */
vAny = n1
vAny = s1
vAny = vAny2
vAny = vUnknown
vAny = nv
/** any也可以赋值给任意类型 */
n1 = vAny
s1 = vAny
vUnknown = vAny
/** unknown 和 any一样,任意值都可以赋值给unknown */
vUnknown = n1
vUnknown = s1
vUnknown = vAny
vUnknown = vUnknown2
/** 但unknown无法赋值给其它类型,除了 any 和 unknown */
n1 = vUnknown2
vAny = vUnknown2
vUnknown = vUnknown2
- 集合的运算
type StringOrNumber = string | number; // 类似string + number ,string 或 number
type StringAndNumber = string & number; // never,二者不会有交集,所以 never
/** 对象或 interface 在用|或&时表现有点相反 */
interface ICat {
eat(): void;
meow(): void;
}
interface IDog {
eat(): void;
bark(): void;
}
declare function Pet(): ICat | IDog;
const pet = new Pet();
pet.eat(); // 成功,只能用二者共有的方法
pet.meow(); // fails
pet.bark(); // fails
declare function AnotherPet(): ICat & IDog;
const anotherPet = new AnotherPet();
anotherPet.eat(); // 成功,类似取了并集
anotherPet.meow(); // succeeds
anotherPet.bark(); // succeeds
type Person = { age: number; name: string; alive: boolean };
type Age = Person["age"]; // number
type Obj = {
hello:string
test:number
}
type ObjKeys = keyof Obj // 'hello' | 'test'
// 数组本质是一个以索引为key的对象
// {0:'hello',1:'world'}
const ff = ['hello', 'world'] as const
const ff2 = ['hello','world']
type ffr<T> = { [p in keyof T]: p }
type ffrr = ffr<typeof ff> // readonly ["0","1"]
type ffrr2 = ffr<typeof ff2> // number[]
type Obj = {
hello:string
test:number
}
type ObjKeys = keyof Obj // 'hello' | 'test'
type NewType = {
[Key in ObjKeys]:any
}
// `in`操作符的右侧通常跟一个联合类型,可以使用`in`来迭代这个联合类型
// 仅演示使用, K为每次迭代的项
K in 'name' | 'age' | 'sex'
K = 'name' // 第一次迭代结果
K = 'age' // 第二次迭代结果
K = 'sex' // 第三次迭代结果
const ff = ['hello', 'world'] as const
// 遍历数组应当使用 p in T[number]
type fffr<T extends readonly any[]> = { [p in T[number]]: p }
type fffrr = fffr<typeof ff> // { hello: "hello";world: "world"; }
const a = [1,2,3] // 值空间
type e = typeof a // number[]
const f = [1, 2, 'me'] as const
type g = typeof f // readonly [1,2,'me']
// const 类型字面量会自动收缩
let str = '123' // 类型为string
const strConst = '123' // 类型为'"123"' ,const意味着值不能被修改,值不能被修改,类型也不会变化,所以它的类型就固化收缩为"123"字
// 用于将字面量值断言为const,不能被修改
// 使用后,字面量值的类型会进一步收缩
// 对象字面量属性会添加上readonly修饰符
// 数组会转变为只读元组
let x = 'hello'
type xr = typeof x // string
let y = 'hello' as const
type yr = typeof y // '"hello"' ,从string 收缩为 '"hello"'
let aa = [1, 2, 3] as const
type aar = typeof aa // readonly [1,2,3]
let bb = { test: 1, hello: 'world' }
type bbr = typeof bb //{test:number;hello:string;}
// 可用于let声明
let cc = { test: 1, hello: 'world' } as const
type ccr = typeof cc // { readonly test: 1; readonly hello: "world"; }
// 可用于var声明
var dd = { test: 1, hello: 'world' } as const
type ddr = typeof dd // { readonly test: 1; readonly hello: "world"; }
type Required<T> = {
[P in keyof T]-?: T[P] // 去除?
}
type Person = {
name: string;
age?: number;
}
// 结果:{ name: string; age: number; }
type result = Required<Person>
// 类型约束,U 必须为联合类型 T 的子集
U extends keyof T
// T 要是 string[]的子集
function Test<T extends string[]>(arg1:T){}
// 基本形式,类似三元表达式
// T 是否是 U 一个子集
T extends U ? 'Y' : 'N'
type result1 = true extends boolean ? true : false // true
type result2 = 'name' extends 'name' | 'age' ? true : false // true
type result3 = [1, 2, 3] extends { length: number; } ? true : false // true
type result4 = [1, 2, 3] extends Array<number> ? true : false // true
// 当用 extends 做条件分支时,若左右有一个为联合类型时,会触发分布式条件类型,类似数学中的分配律
// 内置工具:交集
type Extract<T, U> = T extends U ? T : never;
type type1 = 'name'|'age'
type type2 = 'name'|'address'|'sex'
// 交集结果:'name'
type result = Extract<type1, type2>
// 推理步骤
'name'|'age' extends 'name'|'address'|'sex' ? T : never
step1: ('name' extends 'name'|'address'|'sex' ? 'name' : never) => 'name'
step2: ('age' extends 'name'|'address'|'sex' ? 'age' : never) => never
result: 'name' | never => 'name'
// infer 本质是延迟推导,可做推导占位用,等到真正推导成功后,它能准确的返回正确的类型
type ReturnType<T> = T extends (...args: any) => infer R ? R : never
const add = (a: number, b: number): number => {
return a + b
}
// 结果: number
type result = ReturnType<typeof add>
type First<T extends any[]> = T extends [/** 首个 */infer Fir, /** reset 操作符代表后续类型全部暂存到 Res 中 */...infer Res] ? Fir : n
type MyIsNever<T> = [T] extends [never] ? true : false
// 为何不能用 T extends never
type MyIsNever2<T> = T extends never ? true : false
type A = MyIsNever2<'str'> //str
type B = MyIsNever2<never> // never 因为MyIsNever2<never> 中的 never 实际上是一个空的联合类型,一项都没有,所以 T extends ... 过程实际上被整体跳过了,所以最后的结果就是 never。
- 在要判断的联合类型上加上
[]
- https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
type MyIsNever<T> = [T] extends [never] ? true : false
/** 直接遍历联合类型自身的范式,extends 自身即可 */
type MyItrUnion<T> = T extends T ? [T] : never
/** 运用了分布式条件类型:在 条件类型 中使用 泛型参数 时,如果泛型参数是 联合类型,则会产生 distributive 的效果。 */
/** https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types */
// 'A'|'B' extends 'A'|'B'=>
// 'A' extends 'A'|'B'=> ['A']
// 'B' extends 'A'|'B'=> ['B']
// => ['A']|['B']
type C = MyItrUnion<'A' | 'B'> // ['A'] | ['B']
type D = [1, 2] | [3, 4]
type E = ['a', 'b'] | ['c', 'd']
type F = [true, ...D, ...E]
// [true, 1, 2, "a", "b"] | [true, 1, 2, "c", "d"] | [true, 3, 4, "a", "b"] | [true, 3, 4, "c", "d"]
- ts中没有循环语句,需要通过递归来模拟额循环
type LengthOfString1<
S extends string,
T extends string[] = []
> = S extends `${infer F}${infer R}`
? LengthOfString1<R, [...T, F]> /** 递归模拟循环 */
: T['length']
- 类似js 可以给类型参数设置默认值
type LengthOfString1<
S extends string,
T extends string[] = [] /** 知识点1,设置临时变量存储数组,默认为[] */
> = S extends `${infer F}${infer R}`
? LengthOfString1<R, [...T, F]>
: T['length']
- ts 类型操作中没有 push 语法,可用 reset 操作符模拟
type LengthOfString1<
S extends string,
T extends string[] = []
> = S extends `${infer F}${infer R}`
? LengthOfString1<R, [...T, F]> /** 知识点2,利用[...T,F]向数组添加元素 */
: T['length']
type LengthOfString1<
S extends string,
T extends string[] = []
> = S extends `${infer F}${infer R}`
? LengthOfString1<R, [...T, F]>
: T['length'] /** 知识点3,通过将字符转为数组,获取 length 获取长度 */
type MyFlatten<T extends unknown[], A extends unknown[] = []> = T extends [
infer First,
...infer Rest
]
? First extends unknown[]
? MyFlatten<
[...First, ...Rest] /** 知识点 ...类似 js 中可以给数组降维 */,
A>
: MyFlatten<Rest, [...A, First]>
: A
type Absolute<T extends number | string | bigint> =
`${T}` extends `-${infer R}` /** 知识点,使用字符串模板将 number 转为string */
? R
: `${T}`
type AB = -1_000_000n
type BC = `${AB}` // "-1000000"
/** 知识点,字符转数字,https://devblogs.microsoft.com/typescript/announcing-typescript-4-8-beta/#improved-inference-for-infer-types-in-template-string-types */
type ParseInt<T extends string> = T extends `${infer Digit extends number}`
? Digit
: n
/** 解法:https://github.com/type-challenges/type-challenges/issues/14094 */
type Flip<T extends Record<string, string | number | boolean>> = {
/** 知识点,通过模板字符串,将 boolean 转换为 string 做为key 用 */
[P in keyof T as `${T[P]}`]: P
}
type StringToUnion<T extends string> = T extends `${infer F}${infer Rest}`
? F | StringToUnion<Rest> /** 知识点,extends语句中可以用联合直接拼接 */
: never
type KebabCase<S extends string> = S extends `${infer S1}${infer S2}`
? S2 extends Uncapitalize<S2> /** 知识点,Uncapitalize将字符串文字类型的第一个字符转换为小写 */
? `${Uncapitalize<S1>}${KebabCase<S2>}`
: `${Uncapitalize<S1>}-${KebabCase<S2>}`
: S
// 参考https://github.com/microsoft/TypeScript/issues/27024#issuecomment-421529650
type Equals<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false;
/** 知识点,没有属性的对象不能用{}判断, 需要用{ [key: string]: never }*/
type t3 = { name: 'test' } extends {} ? true : false // true
type t4 = { name: 'test' } extends { [key: string]: never } ? true : false // false
type t5 = {} extends { [key: string]: never } ? true : false // true
- 非联合类型排除掉自身后只剩下 never,可通过此判断是否联合类型
type IsUnion<T, U = T> = [T] extends [never] /** 排除 never */
? false
: T extends T /** 遍历联合类型 */
? /** 知识点,非联合类型排除掉自身后只剩下 never,可通过此判断是否联合类型 */
/** type CC = Exclude<string, string>=>never */
[Exclude<U, T>] extends [never]
? false
: true
: never
- 通过 as 进行 key-remapping ,在key-remapping 中可以用 extends ,infer等
- 常用在对象 key 遍历时,对 key 重新映射,示例1
- 文档:https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#key-remapping-via-as
type EventConfig<Events extends { kind: string }> = {
/** 遍历Events并取 E['kind']做 key 名 */
[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>
// {
// square: (event: SquareEvent) => void;
// circle: (event: CircleEvent) => void;
// }
PropertyKey
是ts
中的global type
,等价于string | number | symbol
index-signature
的类型是string | number | symbol
/** 知识点,PropertyKey是ts 中的global type,等价于string | number | symbol */
type RemoveIndexSignature<T, P = PropertyKey> = {
/** 遍历T获得 key 并 re-mapping */
/** 知识点:index-signature 的取类型是 string | number | symbol */
/** 参考:https://www.totaltypescript.com/concepts/propertykey-type */
/** 如果 P(string | number | symbol) 是 Key 的子集,则说明是 index-signature,则剔除 */
[Key in keyof T as P extends Key
? never
: /** 如果 Key 是 P的子集,则说明是需要保留的 Key */
Key extends P
? Key
: never]: T[Key]
/** 知识点,模板字符串+infer 可实现类似正则匹配效果 */
type CheckPercentageSign<S> = S extends `${infer N}%` ? [N, '%'] : [S, '']
type CheckSign<Sign> = Sign extends '+' | '-' ? Sign : never
/** 知识点,模板字符串+infer 可实现类似正则匹配效果 */
type CheckPercentageSign<S> = S extends `${infer N}%` ? [N, '%'] : [S, '']
type PercentageParser<A extends string> = A extends `${CheckSign<
/** 知识点,infer的类型可当参数传递 */
infer L
>}${infer R}`
? [L, ...CheckPercentageSign<R>]
: ['', ...CheckPercentageSign<A>]
/** 知识点,模板字符串匹配时,可不用 infer 接收,用string表明后面是 string 即可 */
type StartsWith<T extends string, U extends string> = T extends `${U}${string}`
? true
: f
// 方法1,通过遍历展开
type IntersectionObj<T> = {
[P in keyof T]: T[P]
}
type AAAA = {test:3} & {name:4} // 悬浮A 时显示的是{ test: 3 } & { name: 4 }
type BBBB = IntersectionObj<AAAA> // 悬浮BBBB时,显示的则是展开的{test:3,name:4}
// 方法2,递归深层次展开
type ExpandRecursively<T> = T extends object
? T extends infer O
? { [K in keyof O]: ExpandRecursively<O[K]> }
: never
: T
// 方法3,用 Omit<T,never>
type CCCC = Omit<AAAA,never> // // 悬浮CCCC时,显示的是展开的{test:3,name:4}
/** 知识点,对象转联合,用 T[keyof T] */
type ObjectToUnion<T> = T[keyof T]
/** 知识点,数组转联合类型用下标*/
['1', '2']['number'] // '1' | '2'
type ArrToUnion<T> = T extends any[] ? T[number] : T
/** 知识点,强制某个类型必须为 类型 A,否则原样返回,请用 T & A */
/** 参考:https://github.com/type-challenges/type-challenges/issues/6733#issuecomment-1136127999 */
type MustString<T> = T & string // string & T 结果也是一样
// 等价于
type MustStringEqual<T> = T extends string ? T : never
type AA = MustString<true> // never
type BBB = MustString<'hello'> // 'hello'
/** 知识点,利用 infer + | 将字符串转换为联合类型,"AB"->""|"A"|"B" */
type StringToUnion2<S> = S extends `${infer F}${infer R}`
? F | StringToUnion2<R>
: S
- 数组和元组的区别之一就是数组的长度是不固定的,类型为 number,而元组长度是固定的,类型为具体的数字
type IsTuple<T> = /** 判断never */ [T] extends [never]
? false
: /** 知识点,判断 readonly,是为了屏蔽 lengtlike 对象{length:3}(数组和元组的长度是只读的) */ T extends readonly unknown[]
? /** 知识点,数组和元组的区别之一就是数组的长度是不固定的,类型为 number,而元组长度是固定的,类型为具体的数字 */ number extends T['length']
? false
: true
: false
/** number extends T['length'] 怎么生效的? */
/** T['length']是 number 时,number extends number -> true */
/** T['length']是 具体的1,4,9 时,number extends 1 -> false,number 的范围比1大 */
/** number extends T['length'] 可以用 T['length'] extends number 替代吗? */
/** T['length']无论为 number 或 1,4,9时,其 extends number 都为 true */
/** number extends number -> true */
/** 1 extends number -> true */
/** 所以不能调换 */
type Join<T extends any[], U extends number | string> = T extends [
/** 知识点:在infer 时用 extends 直接将 First 推断为 string */
infer F extends string,
...infer R
]
? R['length'] extends 0
? `${F}`
: `${F}${U}${Join<R, U>}`
: ''
// 递归+infer 取值
type A<T> = T extends [infer F,...infer R]?A<R>:never
type LastIndexOf<T extends unknown[], U> = T extends [
/** 知识点:从后往前遍历数组 */
...infer Rest,
infer Last
]
? MyEqual<Last, U> extends true
? /** 知识点:用前面数组的 length 代表当前元素索引 */
Rest['length']
: LastIndexOf<Rest, U>
: -1
/** 思路:先转成字符串,再和 bigint 比较 */
/** 知识点,bingint 只能为整数https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/BigInt */
type Integer<T extends number> = `${T}` extends `${bigint}` ? T : never
/** 知识点,ts中的 valueOf 可以返回类型值的原始类型 */
type a = 3
type b<T> = T extends { valueOf: () => infer R } ? R : T
type c = b<a> // number
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<number>>>; //number
type C = Awaited<boolean | Promise<number>>; // boolean | number
interface Todo {
title: string;
description: string;
}
type A = Partial<Todo>
/**
type A = {
title?: string | undefined;
description?: string | undefined;
}
*/
interface Props {
a?: number;
b?: string;
}
type A = Required<Props>
/**
type A = {
a: number;
b: string;
}
*/
interface Todo {
title: string;
}
type A = Readonly<Todo>
/**
type A = {
readonly title: string;
}
*/
interface CatInfo {
age: number;
breed: string;
}
type CatName = "miffy" | "boris" | "mordred";
type A = Record<CatName, CatInfo>
/**
type A = {
miffy: CatInfo;
boris: CatInfo;
mordred: CatInfo;
}
*/
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
/**
type TodoPreview = {
title: string;
completed: boolean;
}
*/
interface Todo {
title: string;
description: string;
completed: boolean;
createdAt: number;
}
type TodoPreview = Omit<Todo, "description">;
/**
type TodoPreview = {
title: string;
completed: boolean;
createdAt: number;
}
*/
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T0 = Extract<"a" | "b" | "c", "a">; // "a"
type T0 = NonNullable<string | number | undefined>; // string | number
type T1 = NonNullable<string[] | null | undefined>; // string[]
declare function f1(arg: { a: number; b: string }): void;
type T0 = Parameters<() => string>; // []
type T1 = Parameters<(s: string) => void>; // [s: string]
type T2 = Parameters<<T>(arg: T) => T>; // [arg: unknown]
type T3 = Parameters<typeof f1>;
/**
type T3 = [arg: {
a: number;
b: string;
}]
*/
type T4 = Parameters<any>; // unknown[]
type T5 = Parameters<never>;// never
type T6 = Parameters<string>; // error
type T7 = Parameters<Function>; // error
type T0 = ConstructorParameters<ErrorConstructor>; // [message?: string | undefined]
type T1 = ConstructorParameters<FunctionConstructor>; // string[]
type T2 = ConstructorParameters<RegExpConstructor>; // [pattern: string | RegExp, flags?: string | undefined]
class C {
constructor(a: number, b: string) {}
}
type T3 = ConstructorParameters<typeof C>; // [a: number, b: string]
type T4 = ConstructorParameters<any>; // unknown[]
type T5 = ConstructorParameters<Function>; // error
declare function f1(): { a: number; b: string };
type T0 = ReturnType<() => string>; // string
type T1 = ReturnType<(s: string) => void>; // void
type T2 = ReturnType<<T>() => T>; // unknown
type T3 = ReturnType<<T extends U, U extends number[]>() => T>; // number[]
type T4 = ReturnType<typeof f1>; // { a: number; b: string;}
type T5 = ReturnType<any>; // any
type T6 = ReturnType<never>; // never
type T7 = ReturnType<string>; // error any
type T8 = ReturnType<Function>; // error any
class C {
x = 0;
y = 0;
}
type T0 = InstanceType<typeof C>; // C
type T1 = InstanceType<any>; // any
type T2 = InstanceType<never>; // never
type T3 = InstanceType<string>; // error any
type T4 = InstanceType<Function>; // error any
function toHex(this: Number) {
return this.toString(16);
}
function numberToString(n: ThisParameterType<typeof toHex> /** Number */) {
return toHex.apply(n);
}
function toHex(this: Number) {
return this.toString(16);
}
const fiveToHex:/** () => string */ OmitThisParameter<typeof toHex> = toHex.bind(5);
/**
* 这段 TypeScript 代码定义了一个 makeObject 函数
* 该函数接受一个 ObjectDescriptor 类型的参数 desc,该类型包含两个属性:data 和 methods。
* 其中,data 属性是一个泛型 D 类型的对象,而 methods 属性是一个泛型 M 类型的对象,同时约束了 methods 中的方法的 this 上下文类型为 D & M。
* 在 makeObject 函数中,首先根据传入的 desc 参数初始化了 data 和 methods 对象。
* 然后,通过展开运算符 { ...data, ...methods } 将这两个对象合并为一个新对象,并通过类型断言 as D & M 将其断言为类型 D & M,最后返回该新对象。
* 通过调用 makeObject 函数,并传入包含 data 和 methods 的对象作为参数,我们可以创建一个具有指定数据和方法的对象。
* 其中,特别值得注意的是,methods 中的方法可以在方法体中使用强类型的 this 上下文
* 这是因为 methods 的类型声明中使用了 ThisType<D & M>,指明了方法中 this 的类型。
* 这样,方法可以访问对象的数据属性,同时也能访问其他方法。
*/
type ObjectDescriptor<D, M> = {
data?: D;
methods?: M & ThisType<D & M>; // 指定methods中的this为D & M
};
function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
let data: object = desc.data || {};
let methods: object = desc.methods || {};
return { ...data, ...methods } as D & M;
}
let obj = makeObject({
data: { x: 0, y: 0 },
methods: {
moveBy(dx: number, dy: number) {
this.x += dx; // Strongly typed this
this.y += dy; // Strongly typed this
},
},
});
obj.x = 10;
obj.y = 20;
obj.moveBy(5, 5);
type A = Uppercase<'hello'> // 'HELLO'
type B = Lowercase<'HELLO'> // hello
type C = Capitalize<'hello'> // Hello
type D = Uncapitalize<'Hello'> // hello
type E = Uncapitalize<'HELLO'> // hELLO