学过TS得都清楚,条件类型的基本语法是:
T extends U ? X : Y;
如果占位符类型U是一个可以被分解成几个部分的类型,譬如数组类型,元组类型,函数类型,字符串字面量类型等。这时候可以通过infer来获取U类型中某个部分的类型。
infer语法的限制如下
infer
只能在条件类型的 extends
子句中使用infer
得到的类型只能在true
语句中使用, 即X中使用使用方法:
type InferArray<T> = T extends (infer U)[] ? U : never;
(infer U)和平时常写的string[],number[]等等是不是很像?这里就是通过(infer U)来获取数组对应的类型。
案例:
type I0 = InferArray<[number, string]>; // string | number
type I1 = InferArray<string[]>; // string
type I2 = InferArray<number[]>; // number
使用方法:
type InferFirst<T extends unknown[]> = T extends [infer P, ...infer _] ? P : never
[infer P, ... infer _]
中infer P获取的是第一个元素的类型,而...infer _
获取的是数组其他剩余元素的数组类型
ps:特别说明下,我们例子汇总不需要使用其他元素的类型,所以用_。
案例
type I3 = InferFirst<[3, 2, 1]>; // 3
使用方法
type InferLast<T extends unknown[]> = T extends [... infer _, infer Last] ? Last : never;
这个和推断数组第一个元素的类型类似,…infer _获取的是最后一个元素之前的所有元素类型,infer Last获取的是最后一个元素的类型。
案例
type I4 = InferLast<[3, 2, 1]>; // 1
使用方法
type InferParameters<T extends Function> = T extends (...args: infer R) => any ? R : never;
…args 代表的是函数参数组成的元组, infer R代表的就是推断出来的这个函数参数组成的元组的类型。
案例
type I5 = InferParameters<((arg1: string, arg2: number) => void)>; // [string, number]
使用方法
type InferReturnType<T extends Function> = T extends (...args: any) => infer R ? R : never;
和前面的推断函数类型的参数类似,=> 后面的infer R代表的就是推断出来的函数的返回值类型。
案例
type I6 = InferReturnType<() => string>; // string
使用方法
type InferPromise<T> = T extends Promise<infer U> ? U : never;
案例
type I7 = InferPromise<Promise<string>>; // string
使用方法
type InferString<T extends string> = T extends ${infer First}${infer _} ? First : [];
案例
type I8 = InferString<"Johnny">; // J
class Animal {
say() {
console.log('say');
}
}
class Dog extends Animal {}
const dog = new Dog();
dog.say();
type C = <T extends { name: string }>(arg: T) => any;
function fn(cb: C) {
cb({ name: '回调' });
}
fn((arg) => {
console.log(arg.name);
});
type Human = {
name: string;
};
type Duck = {
name: string;
};
type Bool = Duck extends Human ? 'yes' : 'no'; // yes
Bool的类型是 ‘yes’这是因为 Human 和 Duck 的类型完全相同,或者说 Human 类型的一切约束条件,Duck 都具备; 需要理解的是,这个A extends B 是指类型A可以分配给类型B, 而不是说类型A是类型B的子集.稍微扩展下来详细说明这个问题:
type Human = {
name: string;
desc: string;
};
type Duck = {
name: string;
};
type Bool = Duck extends Human ? 'yes' : 'no'; // no
当我们给Human加上一个desc属性,发现此时Bool 是’no’ 这是因为Duck没有类型为string的desc属性,类型Duck不满足类型Human的类型约束.因此 A extends B 是类型A可以分配给类型B 而不是说类型A是类型B的子集,理解extends在类型三元表达式里的用法非常重要。
例:
const dataType2: Partial<ApiKey> = {
name: 'json'
}
例:
interface People {
name: string
age: number
}
type somePeople = Pick<People,'name'>
let onlyName:somePeople = {
name:"112"
}
选择了接口中的name属性,那么该属性就必须含有
例:
// 必选参数
interface People1 {
name?: string;
age?: number;
}
// 类型 "{ name: string; }" 中缺少属性 "age",但类型 "Required<People>" 中需要该属性
const person2: Required<People1> = {
name:"11"
}
例:
nterface People3 {
name: string
age: number,
dog:{
name:string
age:number
}
}
const xiaoling: Readonly<People3> = {
name: '小凌', // 只读
age: 18, // 只读
dog:{
age:1,
name:'大黄'
}
}
// 但是是浅层的
xiaoling.name = '大凌' // 无法分配到 "name" ,因为它是只读属性。
xiaoling.dog.age = 2 // 可以
首先是联合类型的介绍,也是一切的起源
let a: string | number | boolean = '123' // 变量a的类型既可以是string,
a = 123 // 也可以是number
a = true // 也可以是boolean
// key of 使用
interface People5 {
name: string;
age: number;
}
// keyof 取 interface 的键
// type keys = "name" | "age"
type keys = keyof People5;
// 假设有一个 object 如下所示,
// 我们需要使用 typescript 实现一个 get 函数来获取它的属性值
const xiaozhang:People5 = {
name: '小张',
age: 12
}
function get<T extends object, K extends keyof T>(o: T, name: K): T[K] {
return o[name]
}
console.log(get(xiaozhang,'age')) // 12
console.log(get(xiaozhang,'name')) // 小张
// error 类型“"address"”的参数不能赋给类型“keyof People”的参数。
console.log(get(xiaozhang,'address'))
::keyof 与 Object.keys 略有相似,只不过 keyof 取 interface 的键::
``
Record用于属性映射 搭配联合类型
type RequestMethods = 'GET'|'POST'| 'DELETE'
type MethodsAny = Record<RequestMethods, any>
let mothod1:MethodsAny = {
GET:"1",
POST:'1',
DELETE:'1'
}
let mothod2:MethodsAny = {
GET:"1",
POST:'1',
DELETE:'1', // 缺少,会报错,因为它为必须存在的
PUT:'111' // error “PUT”不在类型“MethodsAny”中
}
搭配接口使用
interface PersonModel {
name:string,
age:number
}
// [x: string]: PersonModel;
type student = Record<string, PersonModel>
let students:student = {
student1:{
name:'小凌',
age:18
},
student2:{
name:'小李',
age:19
}
}
``
ts中可以排除 联合类型 中一部分的内容 注意:Exclude用来操作联合类型的
type havTypes = 'name' | 'age' | 'sex'
type someTypes = Exclude<havTypes,'sex'>
const myTypes1:someTypes = 'name' // 可以
const myTypes2:someTypes = 'sex' // 错误 不能将类型“"sex"”分配给类型“someTypes”
ts中就是将接口或者类型的键值对删除一部分
// 省略
interface People {
name: string;
age: number;
}
type somePeople = Omit<People,'name'>
type somePeople = {
age: number;
}
创建一个数组,数组中的索引不允许被修改 我们知道当我们使用const创建对象或者数组时,其实是可以修改其内部属性的,但是有的时候我们可能不需要其内部能够被修改,这时候我们就需要用ReadonlyArray了 实现的方式有两种:
// 方法1:通过类型断言的方式
const arr = ['杜兰特','欧文','科比','乌布雷'] as const
arr[3] = '帅哥'; // 报错 无法分配到 "3" ,因为它是只读属性
// 方法2:使用ReadonlyArray
const arr2 : ReadonlyArray<string> = ['杜兰特','欧文','科比','乌布雷']
arr2[3] = 'JedXu' // 报错 类型“readonly string[]”中的索引签名仅允许读取
你是不是就想知道as const和ReadonlyArray这两者的区别在哪里? 区别在于as const是深层次的,尽管数组内放的对象,对象内部数据也是不能被修改的。ReadonlyArray则是‘浅层’的。
type ToMutable<T> = {
-readonly [Key in keyof T]: T[Key]
}
给索引类型 T 的每个索引去掉 readonly 的修饰,其余保持不变。
同理,也可以去掉可选修饰符:
type ToRequired<T> = {
[Key in keyof T]-?: T[Key]
}
给索引类型 T 的索引去掉 ? 的修饰 ,其余保持不变。
可以在构造新索引类型的时候根据值的类型做下过滤:
type FilterByValueType<
Obj extends Record<string, any>,
ValueType
> = {
[Key in keyof Obj
as Obj[Key] extends ValueType ? Key : never]
: Obj[Key]
}
类型参数 Obj 为要处理的索引类型,通过 extends 约束为索引为 string,值为任意类型的索引类型 Record<string, any>。 类型参数 ValueType 为要过滤出的值的类型。 构造新的索引类型,索引为 Obj 的索引,也就是 Key in keyof Obj,但要做一些变换,也就是 as 之后的部分。 如果原来索引的值 Obj[Key] 是 ValueType 类型,索引依然为之前的索引 Key,否则索引设置为 never,never 的索引会在生成新的索引类型时被去掉。 值保持不变,依然为原来索引的值,也就是 Obj[Key]。 这样就达到了过滤索引类型的索引,产生新的索引类型的目的:
type NewType = Uppercase<'aaa'>; // AAA 全大写
type NewType = Lowercase<'AAA'>; // aaa 全小写
type NewType = Capitalize<'aaa'>; // Aaa 首字母大写
type NewType = Uncapitalize<'AAA'>; // aAA 首字母小写