TypeScript - 映射类型 和 条件类型

317 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第15天,点击查看活动详情

映射类型

有的时候,一个类型需要基于另外一个类型,但是你又不想拷贝一份,这个时候可以考虑使用映射类型

interface Type {
  name: string;
  age: string;
}

// 映射类型 只能使用类型别名 不能使用接口
// 因为使用interface定义映射类型,key中的[xxx]会被识别为索引类型
type MapType<T> = {
  // keyof T ->
  //      1. T必须是一个对象类型
  //      2. keyof会将对象的所有键取出并组成对应的联合类型

  // valueof T -> type valueof<T> = T[keyof T]
  //      1. TS中是没有valueof 操作符的
  //      2. 可以结合keyof创建一个valueof 工具类型
  //      3. 通过该工具类型 可以返回一个对象的所有值组成的联合类型

  // T[key] -> 根据key 取出对应的 value 的对应类型
  // key in 联合类型 -- key必须是联合类型中的某一种
  [key in keyof T]: T[key]
}

type newType = MapType<Type>
/*
  typeof newType
  -> {
        name: string;
        age: string;
      }
*/

映射修饰符

修饰符说明
readonly用于设置属性只读
?用于设置属性可选
interface Type {
  name: string;
  age: number;
}

type MapType<T> = {
  readonly [prop in keyof T]?: T[prop]
}

type newType = MapType<Type>

/*
  type newType = {
    readonly name?: string | undefined;
    readonly age?: number | undefined;
  }
*/

你可以通过前缀 - 或者 + 删除或者添加这些修饰符,如果没有写前缀,相当于使用了 + 前缀

interface Type {
  name?: string;
  age?: number;
}

type MapType<T> = {
  // +readonly 属性添加只读属性 默认就是+ 所以+ 可以省略
  // -? 表示属性 去除只读属性 设置为必传属性
  +readonly [prop in keyof T]-?: T[prop]
}

type newType = MapType<Type>
/*
  =>
    type newType = {
      readonly name: string;
      readonly age: number;
    }
*/

条件类型

很多时候,日常开发中我们需要基于输入的值来决定输出的值,同样我们也需要基于输入的值的类型来决定输出的值的类型

条件类型(Conditional types)就是用来帮助我们描述输入类型和输出类型之间的关系

SomeType extends OtherType ? TrueType : FalseType

我们可以将之前的函数重载使用条件类型进行重写

// 函数重载
function sum(num1: number, num2: number): number
function sum(num1: string, num2: string): string

function sum(num1: any, num2: any) {
  return num1 + num2
}
// 条件类型
// num1 和 num2 的类型 都是T 确保了 num1 和 num2的类型必须一致
// T extends number | string 确保了传入的T的类型不是string就是number
type Sum = <T extends number | string>(num1: T, num2: T) => T extends number ? number : string

const sum: Sum = (num1: any, num2: any) => num1 + num2

// 当我们不主动传入泛型的值的时候,tsc会自动推导T的值
console.log(sum(1, 2))
console.log(sum('1', '2'))

类型推断 - infer

条件类型提供了 infer 关键字可以从正在比较的类型中推断类型,然后在 true 分支里引用该推断结果

// 传入一个函数,推导出函数的返回值类型
// T extends (...args: any[]) => any --- 确保传入的泛型是函数类型
// T extends (...args: any[]) => infer R
//   --- infer R 表示根据传入的泛型值推导类型并存入变量R中
//   --- 也就是说 R是自己定义的变量 -- 约定为 一个大写的字母
type MyReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : never

// 传入一个喊,推导出函数的参数类型
type MyParamerType<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? P : never

function foo(arg1: string, arg2: number) {
  console.log('foo')
}

type returnType = MyReturnType<typeof foo> // typeof returnType -> void
type paramerType = MyParamerType<typeof foo> // typeof paramerType -> [arg1: string, arg2: number]

分发条件类型

当在泛型中使用条件类型的时候,如果传入一个联合类型,就会变成 分发的(distributive)

type toArray<T> = T[]

type arr1 = toArray<number> // typeof arr1 -> number[]
type arr2 = toArray<number | string> // typeof arr2 -> (number | string)[]
type toArray<T> = T extends any ? T[] : never

type arr1 = toArray<number> // typeof arr1 -> number[]

//  toArray内部使用了条件类型 所以 toArray<number | string> === toArray<number> | toArray<string>
type arr2 = toArray<number | string> // typeof arr2 -> number[] | string[]

as

type obj = {
  name: string;
  age: number;
}

type MyOmit<T, K> = {
  // 在条件类型中,keyof关键字后边也可以加速as关键字
  // 此时会将符合条件的联合类型 交给as后的语句进行执行
  // P in keyof T as P extends K ? never : P
  // -> 即需要满足P in keyof T 又需要满足  P extends K ? never : P
  [P in keyof T as P extends K ? never : P]: T[P]
}

type foo = MyOmit<obj, 'name'>