keyof 和 In
keyof: 将interface/type转为联合类型
in: 作用于联合类型, 用于枚举联合类型的值(注:不可用于interface中!)
const person = {
name: 'aa',
age: 1
}
interface IPerson {
name: string
age: number
}
type UnionP = keyof typeof person // 'name' | 'age'
type UnionP1 = keyof IPerson // 'name' | 'age'
type P = {
[p in UnionP1]: string
} // { name: string, age: string}
type p1 = {
[p in UnionP1]: string
} // { name: string, age: string}
interface T0{
name: string
age: number
}
keyof T0 用于获取key组成的联合类型 => 'name' | 'age'
T0[keyof T0] 用于获取value组成的联合类型 => string | number
| 和 &的区别
经过实验
IProps1 & IProps2 - 交叉类型 : 取并集
IProps1 | IProps2 - 联合类型 = Partical<IProps1 & IProps2>
即
IProps1:{
name: string
age: number
}
IProps2:{
school: string
}
IProps1 & IProps2类型的变量必须同时包含name、age、school
IProps1 | IProps2只要满足IProps1或IProp2其中一个即可,可以多不能少,如 { school:string, name: string} 是可以通过的,因为满足了IProps2
实现真正的「或」逻辑
我们会发现,编写类型时使用的 | 并不是我们理解的或
如
interface IProps1 {
firstName: string
lastName: string
}
interface IProps2 {
fullName: string
}
type IPerson = IProps1 | IProps2
期望:ts报错
实际:通过ts校验
const p1 = {
firstName: 'jenson',
lastName: 'liu',
fullName: 'jensonliu'
}
我也不知道这是设计失误还是设计本意,如果想模拟出真正的或逻辑,还是需要一点黑科技的。
思路
使用never类型
interface IProps1 {
firstName: string
lastName: string
fullName?: never
}
interface IProps2 {
firstName?: never
lastName?: never
fullName: string
}
type IPerson = IProps1 | IProps2
这就是手动告诉编译器,fullName存在时firstName和lastName都为never,反之亦然
改进
当然,我们不能对每个interface都去手动写never,因此可以抽取成通用的类型方法
type ExcludeToNever<T, V> = {
[key in keyof Omit<V, keyof T>]?: never; // V中存在而T中不存在的属性
} & {
[key in keyof T]: T[key]; // T的原有属性
};
ExcludeToNever传入两个interface,即T和V, 将V中存在而T中不存在的属性设置为never并塞入T中
然后
type IOr<F, S> = ExcludeToNever<F, S> | ExcludeToNever<S, F>
这样一来就将两个interface中彼此不存在的key都用never填充了
extends
用于继承
interface IProps extends IBaseProps { }
用于三目运算
type IProps<T,U> T extends U ? X : Y
当T、U为interface、type时,extends单纯用于判断继承关系
但当T、U为联合类型时,该运算会遵循类似分解因式的规范
a(b+c) = ac + bc
即
T = 'a' | 'b'
T extends U => 'a' extends U | 'b' extends U
利用这个性质可以完成一些工具类型
T = 'a' | 'b'
U = 'b' | 'c' | 'd'
// 求交集
Intersection<T,U> = T extends U ? T : never
Intersection<'a'|'b', 'b'|'c'|'d'>
= 'a'|'b' extends 'b'|'c'|'d'
= 【'(a' extends 'b'|'c'|'d') ? 'a' : never】 | 【('b' extends 'b'|'c'|'d') ? 'b' : 'never'】
= 'b'
------------------------------------------------------------------
// 求差集
Diff<T, U> = T extends U ? never : T
原理同上
infer
infer是被用在extends中, 用于推导泛型的关键字。
我的理解中,就是将泛型声明为变量并使用的东西,举个例子
我们希望有一个工具类型,ArrChildType<T>来推导数组元素的类型,我们可以这么写
type ArrChildType<T> = T extends Array<P> ? P : T // error Exported type alias 'ArrChildType' has or is using private name 'P'.
type result = ArrChildType<Array<number>>
但会发现报红了,泛型P不能这样使用,于是infer就派上用场了
type ArrChildType<T> = T extends Array<infer P> ? P : T // success
type result = ArrChildType<Array<number>> // number
Parameters
用于获取函数的入参类型
注:Parameters返回结果为tuple数组类型
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>; // [arg: {a: number, b: string}]
type T4 = Parameters<any>; // unknown[]
type T5 = Parameters<never>; // never
ReturnType
用于获取函数返回值类型
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
as const
as const 被称为 const 类型断言,const 类型断言告诉编译器,要将这个表达式推断为最具体的类型,如果不使用它的话,编译器会使用默认的类型推断行为,可能会把表达式推断为更通用的类型。
举个例子, 有这样一个数据结构
const arr = ]
{
name: 'a1',
type: 'type1'
},
{
name: 'a2',
type: 'type2'
},
{
name: 'a3',
type: 'type3'
}
] as const
我们想把所有的type抽出来做一个联合类型
IType = 'type1' | 'type2' | 'type3', 我们会写成这样
type IType = typeof arr[number]['type']
其中arr[number]是指取arr中的item类型
我们会发现如果没有加as const,会得到string,也就是上面所说的,被推断成了更通用的类型,编译器认为你的type是可能变动的。
当加上了as const,结果是'type1' | 'type2' | 'type3'
下面这个例子也是同理
const Status = {
suc: 0,
fail: -1,
pending: 1
} as const
type IStatus = keyof typeof Status // 'suc' | 'fail' | 'pending'
type IStatusCode = typeof Status[keyof typeof Status] // 0 | -1 | 1 若没有as const则为number
替换对象中的key对象类型
思路:先用Omit剔除传入的key枚举,然后 & 上传入的新对象类型
单属性替换
/**
* 替换对象中的属性
* e.g type T= ReplaceKey<typeof obj, 'name'., string>
*/
export type ReplaceKey<O, K extends keyof O, T> = Omit<O, K> & { [p in K]: T }
// K extends keyof O 约束传入的key类型必须在obj内
批量替换
/**
* 批量替换对象中的属性
*/
export type ReplaceKeys<O, K>
= Omit<O, keyof K> & { [p in keyof K]: K[p] } // p in typeof K枚举key值,K[p]取当前key的类型值
const obj = {
key1: 1,
key2: 2
}
type T= ReplaceKeys<typeof obj, { key1: string, key2: string }>
// or
type T= ReplaceKeys<typeof obj, { [p in keyof typeof obj]: string }>
// => { key1: string, key2: string}
保留/剔除对象类型中指定类型值
type T0 = {
name: string
age: never
}
// 曲线救国,如果value!==Condition, 就把value改为和key一样用于最后[keyof T]取值
type GetKeyByValueType<T, Condition> = Pick<
T,
{
[key in keyof T]: T[key] extends Condition ? key : never
}[keyof T]
>
// 用Omit来取反
type excludeKeyByValueType<T, Condition> = Omit<
T,
{
[key in keyof T]: T[key] extends Condition ? key : never
}[keyof T]
>
// 获取为never的
type T = GetKeyByValueType<T0, never> // { age: never }
// 剔除为never的
type T2 = excludeKeyByValueType<T0, never> // { name: string }
is
is 通常用于做类型保护,常用于判断类型的工具Function中
假设有一个判断是否为字符串数组的方法
const isStringArr = (params: unknown) : boolean => {
if(!(params instanceof Array)){
return false
}
return params?.every(item=>typeof item === 'string')
}
const s1 = ''
const result = isStringArr(s1) ? s1.join(',') : '' // error
会发现s1.join除爆红,typescript认为s1还是定义时的'',我们用is来保护该方法的返回值
const isStringArr = (params: unknown) : params is string[] => {
if(!(params instanceof Array)){
return false
}
return params?.every(item=>typeof item === 'string')
}
const s1 = ''
const result = isStringArr(s1) ? s1.join(',') : '' // ok
总结:
- 在使用类型保护时,TS 会进一步缩小变量的类型。例子中,将类型从 any 缩小至了 string[]
- 类型保护的作用域仅仅在 if/三元表达等判断语句后的块级作用域中生效
求交集
前置API: Extract
type Extract<T, U> = T extends U ? T : never;
联合类型的交集
type A = 'a' | 'b' | 'c'
type B = 'a' | 'd'
type UK = Extract<A, B> // 'a'
原理:联合类型做extends时会类似分配律的形式进行
如 a | b | c extends a | d = [a extends a | d] | [b extends a | d]
最终结果会将never过滤掉,因此得到的是交集
interface的交集
type IO1 = {
name: string
age: number
school: string
}
type IO2 = {
school: string
sex: number
}
type UK = Extract<keyof IO1, keyof IO2>
原理:转为联合类型再用Extract
剔除interface中value为某种类型的项
type PickWithoutConToUnion<T, Con> = { [key in keyof T]: Con extends T[key] ? never : key }[keyof T]
type PickWithCon<T, K> = Pick<T, PickWithoutConToUnion<T,K>>
type Result = PickWithCon<IO1,string>
原理:PickWithoutConToUnion 先将interface中的符合过滤条件的value全部转成never,不符合过滤条件的转为key值,然后把value转为联合类型,联合类型会过滤掉never,剩下的就是没有被过滤掉的key值联合类型了,最后再Pick一下