typescript进阶(二)工具类型

211 阅读5分钟
在上篇中主要记录了使用ts的基本知识,本文主要记录如何实现ts内置的工具类型以及更多进阶版的工具类型。如果有问题,欢迎大佬指正。

类型体操其实是不难的,更多的套路。更重要的是上一张章节的内容

八:工具类型

工具类型可以理解为类似我们js中写的公共的工具方法。工具类型也可以称作类型体操,它是类型编程的一部分,在很多库中,函数的重载和参数的限制也是比较复杂的

内置工具类型

属性修饰用途
  • Partial可选。 需要注意一个属性可选,不等于 原类型 | undefined,而是这个属性可以没有。这是因为ts是结构类型
type Partial<T> = { [p in keyof T]?: T[p] } 
  • Required必需
type Required<T> = { [p in keyof T] -?: T[p] }
  • Readonly只读
type Readonly<T> = { readonly [p in keyof T] : T[p] }
  • Mutable非只读
type Mutable<T> = { -readonly [p in keyof T] : T[p] }
结构用途
  • Pick提取
type Pick<T, K extends keyof K> = { [key in K]: T[key] }
  • Omit剔除
type Omit<T,K extends keyof any> = Pick<T, Exclude<keyof T, K>>
  • Record定义,其实就是用了类型工具:索引签名
type Record<K, V> = { [name: K]: V }
联合类型
  • 并集 a|b
  • 交集
type Extract<T, K> = T extends K ? T : never
  • 差集
type Exclude<T,K> = T extends K ? never : T
模式匹配
  • 参数Parames
type Parames<T extends ()=>unknown> = 
    T extends (...reset: infer P) => unknown ? P : never
  • 结果ReturnType
type ReturnType<T extends () => unknown> = 
  T extends (...args: any) => infer R ? R : any;

字符串大小写

  • 字符串大写Uppercase
  • 字符串小写Lowercase
  • 首字母大写Capitalize
  • 首字母小写Uncapitalize
  • 上述字符串相关的这几个工具类型,你从ts内部是看不到具体实现的。实际上,ts底层就是调用js的方法来实现的

模板字符串类型

  • 与js中模板字符串是一样的概念。接口的作用就是描述对象对外的接口,定义、限制了对象的结构。而模板字符串类型的作用与接口的作用是比较接近的
  • 插槽内可以是单个字面量类型、上下文访问到的类型、字面量联合类型、原始类型

通过上下文类型

(就跟js的作用域链似的)

type World = 'world'
type Greet = `hello ${World}`

传入原始类型

  • type Greet = hello ${string};这个时候Greet类型并不会变成 Hello string,而是保持原样。这也意味着它并没有实际意义,此时就是一个无法改变的模板字符串类型,但所有 Hello 开头的字面量类型都会被视为 Hello ${string} 的子类型,如 Hello Linbudu、Hello TypeScript
  • 传入联合类型,type Phone = ${'iphone' | 'xiaomi'} - ${8 | 16}g - ${'白色‘ | ’黑色' };传入联合类型会自动组合成可能的类型,最终type Phone = 'iphone - 8g - 白色‘ | ... ; 如果组合出的类型里面有的不需要,例如iphone就没有16g的,很简单,Exclude剔除掉就行
  • 泛型传入
type Greet<T extends string | number | boolean | 
  null | undefined | bigint> = `hello ${T}`
  • 只有这些类型可以传入string | number | boolean | null | undefined | bigint

  • 映射类型 + as这样的语法,叫做重映射。

type obj = { [k in "name" | "age" as `my-${k}`]: number };
  • 基于重映射,重新实现PickByValue
// 之前
type ExpectPropKey<T extends object, valueType> = {
  [k in keyof T]-?: T[k] extends valueType ? k : never;
}[keyof T];
type PickByValue<T extends object, valueType> = Pick<
  T,
  ExpectPropKey<T, valueType>
>;

// 现在
type PickByValue<T, ValueType> = {
  [name in keyof T as T[name] extends ValueType ? 
    name : never]: T[name];
};

工具类型进阶

内置工具类型进阶

  • 递归可选
type DeepPartial<T extends object> = { [k in keyof T]?: 
  T[k] extends object ? DeepPartial<T[k]> : T[k]
  • 递归必需

type DeepRequired<T extends object> = {
  [K in keyof T]-?: T[K] extends object ? 
    DeepRequired<T[K]> : T[K];
};
  • 递归只读
type DeepReadonly<T extends object> = {
  readonly[K in keyof T]: T[K] extends object 
  ? DeepReadonly<T[K]> : T[K];
};
  • 递归非只读
type DeepMutable<T extends object> = {
  -readonly [K in keyof T]: T[K] extends object ? DeepMutable<T[K]>
  : T[K];
};

属性修饰用途进阶:修饰部分

也就是拆分处理后,再组合

  • 部分可选
type MarkPropsAsOption<T extends object, K extends keyof T
  > = Partial<
  Pick<T, K>
> &
  Omit<T, K>;
  • 部分必需
type MarkPropsAsRequired<T extends object, K extends keyof T> = 
  Required<Pick<T, K>> & Omit<T, K>;
  • 部分只读
type MarkPropsAsReadonly<T extends object, K extends keyof T> = 
  Readonly< Pick<T, K> > & Omit<T, K>;
  • 部分非只读
type Mutable<T> = {
  -readonly [k in keyof T]: T[k];
};
type MarkPropsAsMutable<T extends object, K extends keyof T> = 
  Mutable< Pick<T, K> > & Omit<T, K>;

结构用途进阶:依据属性值类型

上面的工具类型,都是依据属性类型,进行的类型编程。也可以通过属性值的类型进行特殊的类型编程

  • PickByValue
// 在联合类型中,never是无效的,会被忽略
// type nameList = 'zcc' | 'ltt' | never
// 实际上想当于type nameList = 'zcc' | 'ltt'
type ExpectPropKey<T extends object, valueType> = {
  [k in keyof T]-?: T[k] extends valueType ? k : never;
}[keyof T];

type PickByValue<T extends object, valueType> = Pick<
  T, ExpectPropKey<T, valueType>>;
  • OmitByValue
type OmitByValue<T extends object, valueType> = Omit<
  T, ExpectPropKey<T, valueType>>;
  • 基于结构的互斥类型,不能同时拥有。比如说一个对象类型里面要么只有success属性,要么只有fail属性,而联合类型是不会满足这个需求的,你想想结构类型兼容性。如果 想要互斥,就应该将该属性值类型设为never。
type Without<T extends object, U extends object> = {
  [key in Exclude<keyof T, keyof U>]: never;
};

type XOR<T extends object, U extends object> =
  | (Without<T, U> & U)
  | (Without<U, T> & T);

模式匹配进阶

  • 提取函数第一个参数
type FirstParams<T extends (...args: any) => any> = T extends (
  f: infer F, ...args: any ) => any ? F : never;
  • 提取函数最后一个参数
type LastParams<T extends (...args: any) => any> = T extends (
  ...args: infer A
) => any
  ? A extends [...any, infer B]
    ? B
    : never
  : never;
  • 提取对象必需属性
// 还记得之前说过一个属性为可选类型相当于什么?
// {name?: string} = {} | {name: string}
// 所以{}是{name?: string}的子集
// 方案一:
type PickByRequiredKeys<T extends object> = {
  [name in keyof T]-?: {} extends Pick<T, name> ? never : name;
}[keyof T];

type PickByRequired<T extends object> = 
  Pick<T, PickByRequiredKeys<T>>;
  
// 方案二: 使用重映射
type PickByRequired<T extends Record<keyof any, unknown>> = {
  [name in keyof T as {} extends Pick<T, name> ? never : name]: T[name]
}
  • 提取对象可选属性
// 方案一
type PickByOptionalKeys<T extends object> = {
  [name in keyof T]-?: {} extends Pick<T, name> ? name : never;
}[keyof T];

type PickByOptional<T extends object> = 
  Pick<T, PickByOptionalKeys<T>>;
  
// 方案二:使用重映射
type PickByOptional<T extends Record<keyof any, unknown>> = { 
  [name in keyof T as {} extends Pick<T, name> ? name : never]: T[name]
}

模板字符串进阶

  • 去除左右的空格
type TrimLeft<T extends string> = T extends ` ${infer A}` 
  ? TrimLeft<A> : T;

type TrimRight<T extends string> = T extends `${infer A} ` 
  ? TrimRight<A> : T;

type Trim<T extends string> = TrimLeft<TrimRight<T>>;
  • 是否存在
type _Include<
  T extends string,
  S extends string
> = T extends `${infer A}${S}${infer B}` ? true : false;

// 因为字面量''只是字面量类型'',也就是自身的子集,所以需要单独判断出来
type Include<T extends string, S extends string> = T extends ""
  ? S extends ""
    ? true
    : false
  : _Include<T, S>;
  • 删除(加个泛型,填充进去就是替换)
type Replace< T extends string, S extends string
  > = T extends `${infer A}${S}${infer B}` 
   ? `${A}${B}` : T;
  • 全部删除(加个泛型,填充进去就是替换)
type ReplaceAll< T extends string, S extends string
  > = T extends `${infer A}${S}${infer B}` 
    ? Replace<`${A}${B}`, S> : T;
  • 分隔符拆分
type Split<
  T extends string,
  Delimiter extends string
> = T extends `${infer A}${Delimiter}${infer B}`
  ? [A, ...Split<B, Delimiter>]
    : T extends Delimiter
    ? []
  : [T];
  • 分隔符合并
type Join< List extends Array<string | number>,
  Delimiter extends string > = List extends [] 
    ? "" : List extends [string | number]
      ? `${List[0]}` : 
        List extends [string | number , ...infer Rest extends (number | string)[]]
      ? `${List[0]}${Delimiter}${Join<Rest, Delimiter>}`
    : string;