TypeScript 类型体操姿势合集<通关总结>--刷完

·  阅读 2674
TypeScript 类型体操姿势合集<通关总结>--刷完

建议做体操之前阅读:

  1. 官方文档
  2. 大佬的文章,受益匪浅

之前在写一个自己的,深感自身的ts写的太渣,所以决心要好好修炼一下ts,不得不的说,只有掌握了这些姿势,才能在运用中写出更好的类型设计

原项目地址:github.com/type-challe…

此外,我很长,你忍一下,实在忍不住,可以mark起来以后再用

忍不住的同学可以直接阅读姿势总结篇

简单

实现pick

实现 TS 内置的 Pick<T, K>,但不可以使用它。

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P]
}
interface Todo {
  title: string
  description: string
  completed: boolean
}

/* 
  A = {
    title: string;
    completed: boolean;
  }
*/
type A = MyPick<Todo, 'title' | 'completed'>
复制代码

实现 Readonly

type MyReadonly<T> = {
  readonly [P in keyof T]: T[P]
}
复制代码

元组转换为对象

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

type TupleToObject<T extends readonly string[]> = {
  [P in T[number]]:P
}

const result: TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
复制代码

注意要加上readonly, 因为 as const 会生成如下的类型:

const tuple: readonly ["tesla", "model 3", "model X", "model Y"]
复制代码

const 断言文档:www.typescriptlang.org/docs/handbo…

第一个元素

实现一个通用First<T>,它接受一个数组T并返回它的第一个元素的类型。

type First<T extends any[]> = T extends [infer F, ...infer R] ? F : never
复制代码

元组文档:www.typescriptlang.org/docs/handbo…

infer文档:www.typescriptlang.org/docs/handbo…

获取元组长度

type Length<T extends readonly any[]> = T['length']
复制代码

元组类型是另一种Array类型,它确切地知道它包含多少元素,以及在特定位置包含哪些类型

扩展 readonly 的表现

数组,元素加上 readonly 为普通形式父集 对象属性的 redonly 不影响类型兼容

type A = [string]
type RA = Readonly<A>

type B = string[]
type RB = Readonly<B>

type IsExtends<T, Y> = T extends Y ? true : false

type AExtendsRA = IsExtends<A, RA> //true

type RAExtendsA = IsExtends<RA, A> //false

type BExtendsRA = IsExtends<B, RB> // true

type RBExtendsB = IsExtends<RB, B> // false

type C = {
  name: string
}
type RC = Readonly<C>
type CExtendsRC = IsExtends<C, RC> // true
type RCExtendsC = IsExtends<RC, C> // true
复制代码

对象只读属性不影响类型兼容:

www.typescriptlang.org/docs/handbo…

stackoverflow.com/questions/5…

数组和元组的只读:

github.com/Microsoft/T…

Exclude

实现内置的Exclude <T,U>

type MyExclude<T, K> = T extends K ? never : T
复制代码

Awaited

获取 Promise<ExampleType> 中的 ExampleType

type Awaited<T extends Promise<any>> = T extends PromiseLike<infer L> ? L :never
复制代码

If

example:

type A = If<true, 'a', 'b'>  // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'
复制代码
type If<C extends boolean, T, F> = C extends true ? T : F
复制代码

Concat

example:

type Result = Concat<[1], [2]> // expected to be [1, 2]
复制代码
type Concat<T extends any[], U extends any[]> = [...T, ...U]
复制代码

Includes

example :

type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`
复制代码
// 将元组转换一个value为true的对象
type Includes<T extends any[], U> = {
  [K in T[number]]: true
}[U] extends true
  ? true
  : false
复制代码

中等

获取函数返回类型

不使用 ReturnType 实现 TypeScript 的 ReturnType<T> 范型。

type MyReturnType<T> = T extends (...argv:any[]) => infer T ? T :never
复制代码

实现 Omit

// one
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P]
}
type MyExclude<T, K> = T extends K ? never : T
type MyOmit<T, K> = MyPick<T, MyExclude<keyof T, K>>


// two
type MyExclude<T, K> = T extends K ? never : T
type MyOmit<T, K> = {
  [P in keyof T as P extends MyExclude<keyof T, K> ? P : never]: T[P]
}
复制代码

实现 Readonly 2

实现一个通用MyReadonly2<T, K>,它带有两种类型的参数TK

K指定应设置为Readonly的T的属性集。如果未提供K,则应使所有属性都变为只读,就像普通的Readonly<T>一样。

type MyReadonly2<T, K=keyof T> = {
  [P in keyof T as P extends K ? never : P]: T[P]
} &
  {
    readonly [P in keyof T as P extends K ? P : never]: T[P]
  }

// upgrade
type MyReadonly2<T, K extends keyof T = keyof T> = {
    readonly [P in K]: T[P]
  } & T;
复制代码

深度 Readonly

实现一个通用的DeepReadonly<T>,它将对象的每个参数及其子对象递归地设为只读。

type IsObjectLiteral<T> = keyof T extends never ? false : true

type DeepReadonly<T> = {
  readonly [P in keyof T]: IsObjectLiteral<T[P]> extends true
    ? DeepReadonly<T[P]>
    : T[P]
}
复制代码

元组转合集

example:

type Arr = ['1', '2', '3']

const a: TupleToUnion<Arr> // expected to be '1' | '2' | '3'
复制代码
type TupleToUnion<T extends any[]> = T[number]
复制代码

可串联构造器

example:

declare const config: Chainable

const result = config
  .option('foo', 123)
  .option('name', 'type-challenges')
  .option('bar', { value: 'Hello World' })
  .get()

// 期望 result 的类型是:
interface Result {
  foo: number
  name: string
  bar: {
    value: string
  }
}
复制代码
type Chainable<P = {}> = {
  option<K extends string, T>
    (
      key: K extends keyof P ? never : K,
      value: T
    ): Chainable<P & {[key in K]: T}>
  get(): P
}
复制代码

最后一个元素

实现一个通用Last<T>,它接受一个数组T并返回其最后一个元素的类型。

example:

type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]

type tail1 = Last<arr1> // expected to be 'c'
type tail2 = Last<arr2> // expected to be 1
复制代码
type Last<T extends any[]> = T extends [...any[], infer Latest] ? Latest : never
复制代码

出堆

实现一个通用Pop<T>,它接受一个数组T并返回一个没有最后一个元素的数组。 example:

type arr1 = ['a', 'b', 'c', 'd']
type arr2 = [3, 2, 1]

type re1 = Pop<arr1> // expected to be ['a', 'b', 'c']
type re2 = Pop<arr2> // expected to be [3, 2]
复制代码
type Pop<T extends any[]> = T extends [... infer R, infer L] ? R :never
复制代码

Promise.all

实现PromiseAll,它接受PromiseLike对象数组,返回值应为Promise<T>,其中T是解析的结果数组。

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

// expected to be `Promise<[number, number, string]>`
const p = Promise.all([promise1, promise2, promise3] as const)
复制代码
declare function PromiseAll<T extends readonly unknown[]>(
  args: readonly [...T]
): Promise<{ [P in keyof T]: T[P] extends Promise<infer R> ? R : T[P] }>
复制代码

参考可变元组:文档        PR

Type Lookup

根据其属性在并集中查找类型。 期望LookUp<Dog | Cat, 'dog'>获得DogLookUp<Dog | Cat, 'cat'>获得Cat

interface Cat {
  type: 'cat'
  breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal'
}

interface Dog {
  type: 'dog'
  breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer'
  color: 'brown' | 'white' | 'black'
}

type MyDog = LookUp<Cat | Dog, 'dog'> // expected to be `Dog`
复制代码
type LookUp<U, T> = U extends { type: T } ? U : never
复制代码

Trim Left

删除开始的空格 example:

type trimed = TrimLeft<'  Hello World  '> // expected to be 'Hello World  '
复制代码
type TWhiteSpace = ' ' | '\n' | '\t'
type TrimLeft<S extends string> = S extends `${TWhiteSpace}${infer R}`
  ? TrimLeft<R>
  : S
复制代码

Trim

去除两侧空格 example:

type trimed = Trim<'  Hello World  '> // expected to be 'Hello World'
复制代码
type TWhiteSpace = ' ' | '\n' | '\t'
type TrimLeft<S extends string> = S extends `${TWhiteSpace}${infer R}`
  ? TrimLeft<R>
  : S
type TrimRight<S extends string> = S extends `${infer R}${TWhiteSpace}`
  ? TrimRight<R>
  : S

type Trim<T extends string> = TrimRight<TrimLeft<T>>

复制代码

关于模板字符串和infer的可以查看这些资料:

infer: www.typescriptlang.org/docs/handbo…

模板字符串的支持:github.com/microsoft/T…

Capitalize

实现 Capitalize 将第一个字母转换为大写

example:

type capitalized = Capitalize<'hello world'> // expected to be 'Hello world'
复制代码
type Capitalize<S extends string> = S extends `${infer L}${infer R}`
  ? `${Uppercase<L>}${R}`
  : ''
复制代码

Replace

example:

type replaced = Replace<'types are fun!', 'fun', 'awesome'> // expected to be 'types are awesome!'
复制代码
type Replace<
  S extends string,
  From extends string,
  To extends string
> = From extends ''
  ? S
  : S extends `${infer L}${From}${infer R}`
  ? `${L}${To}${R}`
  : S
复制代码

ReplaceAll

example:

type replaced = ReplaceAll<'t y p e s', ' ', ''> // expected to be 'types'
复制代码
type ReplaceAll<
  S extends string,
  From extends string,
  To extends string
> = From extends ''
  ? S
  : S extends `${infer L}${From}${infer R}`
  ? `${ReplaceAll<L, From, To>}${To}${ReplaceAll<R, From, To>}`
  : S
复制代码

追加参数

example:

type Fn = (a: number, b: string) => number

type Result = AppendArgument<Fn, boolean> 
// 期望是 (a: number, b: string, x: boolean) => number
复制代码
type AppendArgument<Fn extends (...s: any[]) => any, A> = Fn extends (
  ...s: infer T
) => infer R
  ? (...s: [...T, A]) => R
  : never
复制代码

Permutation

实现将联合类型转换为包含联合排列的数组的排列类型

example:

type perm = Permutation<'A' | 'B' | 'C'>; // ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']
复制代码
//分布式 确定 第一个元素,再把第一个元素除去,递归确定剩余元素
type Permutation<T, P = T> = [T] extends [never]
  ? []
  : T extends any
  ? [T, ...Permutation<Exclude<P, T>>]
  : never
复制代码

Length of String

计算字符串的长度

type LengthOfString<S extends string, T extends any[] = []> = S extends `${infer L}${infer R}`
  ? LengthOfString<R, [...T, L]>
  : T['length']
复制代码

Flatten

example:

type flatten = Flatten<[1, 2, [3, 4], [[[5]]]]> // [1, 2, 3, 4, 5]
复制代码
type Flatten<T extends any[]> = T extends [infer First, ...infer Rest]
  ? [...(First extends any[] ? Flatten<First> : [First]), ...Flatten<Rest>]
  : T
复制代码

Append to object

实现一种向接口添加新字段的类型

example:

type Test = { id: '1' }
type Result = AppendToObject<Test, 'value', 4> // expected to be { id: '1', value: 4 }
复制代码
type Merge<T> = {
  [P in keyof T]: T[P]
}

type AppendToObject<T, U extends string, V> = Merge<
  { [P in keyof T]: T[P] } & { [P in U]: V }
>
复制代码

需要注意一下这种情况:

交集相等问题

type Equal<T, D> = (<S>() => S extends T ? 1 : 2) extends <S>() => S extends D
  ? 1
  : 2
  ? true
  : false

type A = {
  name: string
  age: number
}
type B = {
  name: string
}
type Merge<T> = {
  [P in keyof T]: T[P]
}

type IsEqual1 = Equal<A, B & { age: number }> //false
type IsEqual2 = Equal<A, Merge<B & { age: number }>> // true
复制代码

Absolute

实现 Absolute。 接收字符串、数字或 bigint 的类型。 输出应该是一个正数字符串

example:

type Test = -100;
type Result = Absolute<Test>; // expected to be "100"
复制代码
type Number = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
type Absolute<T extends number | string | bigint> =
  `${T}` extends `${infer First}${infer Reset}`
    ? `${First}` extends `${Number}`
      ? `${First}${Absolute<Reset>}`
      : `${Absolute<Reset>}`
    : ''
复制代码

String to Union

example:

type Test = '123';
type Result = StringToUnion<Test>; // expected to be "1" | "2" | "3"
复制代码
type StringToUnion<T extends string> = T extends `${infer First}${infer Reset}`
  ? First | StringToUnion<Reset>
  : never
复制代码

Merge

将两个类型合并为一个新类型。相同的key的情况下,第二个覆盖第一个

type IntersectionToObj<T> = {
  [P in keyof T]:T[P]
}

type Merge<F, S> = IntersectionToObj<Omit<F, keyof S> & S>
复制代码

CamelCase

example: for-bar-baz -> forBarBaz

type ConcatDash<S extends string> = `-${S}`
type CamelCase<S extends string> = S extends `${infer L}-${infer M}${infer R}`
  ? M extends '-'
    ? `${L}-${CamelCase<ConcatDash<R>>}`
    : M extends Uppercase<M>
    ? `${L}-${M}${CamelCase<R>}`
    : `${L}${Uppercase<M>}${CamelCase<R>}`
  : S
复制代码

KebabCase

example: FooBarBaz -> for-bar-baz

type StringToUnion<T extends string> = T extends `${infer First}${infer Reset}`
  ? First | StringToUnion<Reset>
  : never

type UpperCases = StringToUnion<'ABCDEFGH'> | StringToUnion<'IJKLMNOPQRSTUVXYZ'>
type Split<S extends string> = S extends `${infer L}${infer R}`
  ? L extends UpperCases
    ? L extends Uppercase<L>
      ? `-${Lowercase<L>}${Split<R>}`
      : `${L}${Split<R>}`
    : `${L}${Split<R>}`
  : S
type DelFirst<T extends string, U extends string> = T extends `-${string}`
  ? U
  : U extends `-${infer R}`
  ? R
  : U

type KebabCase<T extends string> = DelFirst<T, Split<T>>
复制代码

Diff

比较两个对象类型 的差别

type Merge<T> = {
  [P in keyof T]: T[P]
}
type Diff<O, O1> = Merge<
  | {
      [P in Exclude<keyof O, keyof O1>]: O[P]
    } &
      {
        [P in Exclude<keyof O1, keyof O>]: O1[P]
      }
>
复制代码

AnyOf

接受Array,如果Array中的任何元素为true则返回true。否则返回false example:

type Sample1 = AnyOf<[1, "", false, [], {}]>; // expected to be true.
type Sample2 = AnyOf<[0, "", false, [], {}]>; // expected to be false.
复制代码
type FalseUnion = false | '' | 0 | Record<string | number, never> | []
type AnyOf<T extends readonly any[]> = T extends [infer First, ...infer Reset]
  ? First extends FalseUnion
    ? AnyOf<Reset>
    : true
  : false```
复制代码

这里需要注意的是 {} 是相对大的集合

type A = { name: string } extends {} ? true : false // true
type C = { name: string } extends Record<string | number, never> ? true : false // false
复制代码

IsNever

example:

type A = IsNever<never>  // expected to be true
type B = IsNever<undefined> // expected to be false
type C = IsNever<null> // expected to be false
type D = IsNever<[]> // expected to be false
type E = IsNever<number> // expected to be false
复制代码
type IsNever<T> = [T] extends [never] ? true : false
复制代码

never是最小的集合,只有never能赋值给never 文档

IsUnion

example:

type case1 = IsUnion<string>  // false
type case2 = IsUnion<string|number>  // true
type case3 = IsUnion<[string|number]>  // false
复制代码

第一种方法

type IsUnion<T> = (T extends any ? (arg: T) => void : never) extends (
  arg: infer U
) => void
  ? [T] extends [U]
    ? false
    : true
  : never
复制代码

利用联合类型逆变的特性,如果是联合类型,则发生逆变为交集类型

第二种方法

type IsUnion<T , U = T> = T extends U? 
  [U] extends [T]?false:true
  :never;
复制代码

利用分配式的特性 如果 T 为联合类型,则发生分配式,此时 T 为联合类型其中的一个子类型,U 为 T

ReplaceKeys

实现一个类型ReplaceKeys,替换联合类型中的键,如果某些类型没有这个键,就跳过替换,类型有三个参数。 example:

type NodeA = {
  type: 'A'
  name: string
  flag: number
}

type NodeB = {
  type: 'B'
  id: number
  flag: number
}

type NodeC = {
  type: 'C'
  name: string
  flag: number
}


type Nodes = NodeA | NodeB | NodeC

type ReplacedNodes = ReplaceKeys<Nodes, 'name' | 'flag', {name: number, flag: string}> // {type: 'A', name: number, flag: string} | {type: 'B', id: number, flag: string} | {type: 'C', name: number, flag: string} // would replace name from string to number, replace flag from number to string.

type ReplacedNotExistKeys = ReplaceKeys<Nodes, 'name', {aa: number}> // {type: 'A', name: never, flag: number} | NodeB | {type: 'C', name: never, flag: number} // would replace name to never
复制代码
type ReplaceKeys<U, T extends string, Y> = {
  [K in keyof U]: K extends T ? (K extends keyof Y ? Y[K] : never) : U[K]
}
复制代码

这里需要注意的是 映射类型 当接收联合类型时也是分布式的

type NodeA = {
  type: 'A'
  name: string
  flag: number
}

type NodeB = {
  type: 'B'
  id: number
  flag: number
}

type NodeC = {
  type: 'C'
  name: string
  flag: number
}

type Nodes = NodeA | NodeB | NodeC
type Map<T> = {
  [P in keyof T]: T[P]
}
// Map<NodeA> | Map<NodeB> | Map<NodeC>
type A = Map<Nodes>
复制代码

Remove Index Signature

实现RemoveIndexSignature<T>,从对象类型中排除索引签名。 example:

type Foo = {
  [key: string]: any;
  foo(): void;
}
type A = RemoveIndexSignature<Foo>  // expected { foo(): void }
复制代码
// An index signature parameter type be either 'string' or 'number'. 
type RemoveIndexSignature<T> = {
  [K in keyof T as string extends K
    ? never
    : number extends K
    ? never
    : K]: T[K]
}
复制代码

参考:www.typescriptlang.org/docs/handbo…

百分比解析器

example:

type PString1 = ''
type PString2 = '+85%'
type PString3 = '-85%'
type PString4 = '85%'
type PString5 = '85'

type R1 = PercentageParser<PString1>  // expected ['', '', '']
type R2 = PercentageParser<PString2>  // expected ["+", "85", "%"]
type R3 = PercentageParser<PString3>  // expected ["-", "85", "%"]
type R4 = PercentageParser<PString4>  // expected ["", "85", "%"]
type R5 = PercentageParser<PString5>  // expected ["", "85", ""]
复制代码
type Num = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
type Operation = '+' | '-'
type PercentageParser<T, U extends string[] = [], N = 0> = N extends 0
  ? T extends `${infer L}${infer R}`
    ? L extends Operation
      ? PercentageParser<R, [L], 1>
      : PercentageParser<T, [''], 1>
    : PercentageParser<T, ['', ''], 2>
  : N extends 1
  ? T extends `${infer L}${infer R}`
    ? L extends Num
      ? PercentageParser<R, [U[0], `${U[1] extends string ? U[1] : ''}${L}`], 1>
      : PercentageParser<T, [U[0], U[1] extends string ? U[1] : ''], 2>
    : PercentageParser<T, [U[0], U[1] extends string ? U[1] : ''], 2>
  : N extends 2
  ? T extends `${infer L}`
    ? L extends '%'
      ? [U[0], U[1], '%']
      : [U[0], U[1], '']
    : [U[0], U[1], '%']
  : never
复制代码

Drop Char

从字符串中删除指定的字符。

example:

type Butterfly = DropChar<' b u t t e r f l y ! ', ' '> // 'butterfly!'
复制代码
type EqualType<T, R> = [T] extends [R] ? ([R] extends [T] ? true : false) : false
type DropChar<
  S extends string,
  C extends string
> = S extends `${infer L}${infer R}`
  ? `${EqualType<L, C> extends true ? '' : L}${DropChar<R, C>}`
  : ''

// Display exceeds recursion limit
// type DropChar<S extends string, C> = S extends `${infer L}${infer R}`
//   ? L extends C
//     ? C extends L
//       ? DropChar<R, C>
//       : `${L}${DropChar<R, C>}`
//     : `${L}${DropChar<R, C>}`
//   : ''

复制代码

MinusOne

给定一个数字(总是正数)作为类型。返回减少1的数字。 example:

type Zero = MinusOne<1> // 0
type FiftyFour = MinusOne<55> // 54
复制代码
type Make10Array<T extends any[]> = [
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T
]
type Make1Array<T, L extends any[] = []> = `${L['length']}` extends T
  ? L
  : Make1Array<T, [...L, 1]>
  
type AddEqualArrayLength<
  T extends string,
  L extends any[] = []
> = T extends `${infer U}${infer R}`
  ? AddEqualArrayLength<R, [...Make10Array<L>, ...Make1Array<U>]>
  : L

type Pop<T extends any[]> = T extends [...infer F, number] ? F : never
type MinusOne<T extends number> = Pop<AddEqualArrayLength<`${T}`>>['length']
复制代码

PickByType

example:

type OnlyBoolean = PickByType<{
  name: string
  count: number
  isReadonly: boolean
  isEnable: boolean
}, boolean> // { isReadonly: boolean; isEnable: boolean; }
复制代码
type EqualType<T, R> = [T] extends [R]
  ? [R] extends [T]
    ? true
    : false
  : false

type PickByType<T, U> = {
  [P in keyof T as EqualType<T[P], U> extends true ? P : never]: T[P]
}
复制代码

StartsWith

example:

type a = StartsWith<'abc', 'ac'> // expected to be false
type b = StartsWith<'abc', 'ab'> // expected to be true
type c = StartsWith<'abc', 'abcd'> // expected to be false
复制代码
type StartsWith<T extends string, U extends string> = T extends `${U}${string}` ? true : false
复制代码

EndsWith

实现EndsWith<T, U>,它接受两个精确的字符串类型并返回T是否以U结尾

type EndsWith<T extends string, U extends string> = T extends `${string}${U}` ? true : false
复制代码

PartialByKeys

指定 对象类型中 keys 为可选

example:

interface User {
  name: string
  age: number
  address: string
}

type UserPartialName = PartialByKeys<User, 'name'> // { name?:string; age:number; address:string }
复制代码
type IntersectionToObj<T> = {
  [K in keyof T]: T[K]
}
type PartialByKeys<T , K = any> = IntersectionToObj<{
  [P in keyof T as P extends K ? P : never]?: T[P]
} & {
  [P in Exclude<keyof T, K>]: T[P]
}>
复制代码

RequiredByKeys

实现一个泛型RequiredByKeys<T, K>,它接受两个类型参数T和K。 K 指定应设置为必需的 T 的属性集。 当未提供 K 时,它应该像正常的 Required<T> 一样要求所有属性。

example:

interface User {
  name?: string
  age?: number
  address?: string
}

type UserPartialName = RequiredByKeys<User, 'name'> // { name: string; age?: number; address?: string }
复制代码
type IntersectionToObj<T> = {
  [K in keyof T]: T[K]
}
type RequiredByKeys<T , K = any> = IntersectionToObj<{
  [P in keyof T as P extends K ? P : never]-?: T[P]
} & {
  [P in Exclude<keyof T, K>]?: T[P]
}>
复制代码

Mutable

实现泛型Mutable<T>,这使得T中的所有属性都是可变的(不是只读的)。

example:

interface Todo {
  readonly title: string
  readonly description: string
  readonly completed: boolean
}

type MutableTodo = Mutable<T> // { title: string; description: string; completed: boolean; }
复制代码
type Mutable<T> = {
  -readonly [K in keyof T]:T[K]
}
复制代码

困难

简单的 Vue 类型

实现类似Vue的类型支持的简化版本。

通过提供函数名称SimpleVue(类似于Vue.extenddefineComponent),它应该正确地推断出计算和方法内部的this类型。

在此挑战中,我们假设SimpleVue接受带有datacomputedmethods字段的Object作为唯一参数,

-data是一个简单的函数,它返回一个公开上下文this的对象,但是您无法访问data中的数据本身或其他计算机值或方法。

-computed是将上下文作为this的函数的对象,进行一些计算并返回结果。计算结果应作为简单的返回值而不是函数显示给上下文。

-methods是函数的对象,其上下文也与this相同。方法可以访问datacomputed以及其他methods公开的字段。 computedmethods的不同之处在于按原样公开的功能。

SimpleVue的返回值类型可以是任意的。

// 这里的推导很神奇 从 any 推导除了 string
type A = Record<string, any>

interface Options<D, C extends A, M> {
  data: (this: void) => D
  computed: C & ThisType<D & C>
  methods: M & ThisType<M & { [P in keyof C]: ReturnType<C[P]> }>
}
declare function SimpleVue<D, C extends A, M>(options: Options<D, C, M>): any
复制代码
// 另外一种直观一些的写法
type ComputedReturnType<T> = T extends Record<string, () => any>
  ? {
      [P in keyof T]: ReturnType<T[P]>
    }
  : never

interface Options<D, C, M> {
  data: (this: void) => D
  computed: C & ThisType<D>
  methods: M & ThisType<M & ComputedReturnType<C>>
}
declare function SimpleVue<D, C, M>(options: Options<D, C, M>): any
复制代码

参考:函数泛型 ThisType

科里化 1

example:

const add = (a: number, b: number) => a + b
const three = add(1, 2)

const curriedAdd = Currying(add)
const five = curriedAdd(2)(3)
复制代码
type Curr<A extends any[], R> = A extends [...infer F, infer L]
  ? Curr<F, (x: L) => R>
  : R

declare function Currying<T>(
  fn: T
): T extends (...argv: infer A) => infer R ? Curr<A, R> : never
复制代码

Union to Intersection

example:

type I = Union2Intersection<'foo' | 42 | true> // expected to be 'foo' & 42 & true
复制代码
type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (
  x: infer R
) => void
  ? R
  : never
复制代码

Get Required

实现高级util类型GetRequired<T>,该类型保留所有必填字段 example:

type I = GetRequired<{ foo: number, bar?: string }> // expected to be { foo: number }
复制代码
type IfEquals<X, Y> = [X] extends [Y] ? ([Y] extends [X] ? true : false) : false
type GetRequired<T> = {
  [P in keyof T as IfEquals<
    { [O in P]: T[P] },
    { [O in P]-?: T[P] }
  > extends true
    ? P
    : never]: T[P]
}
复制代码
// 基于
type F = {
  name?: string
}
type R = {
  name: string
}

// 加上 [] 是为了防止联合类型进行分配模式
type IfEquals<X, Y> = [X] extends [Y] ? ([Y] extends [X] ? true : false) : false
type G = IfEquals<F, R>    // false

复制代码

Get Optional

实现高级util类型GetOptional<T>,该类型保留所有可选字段

type I = GetOptional<{ foo: number, bar?: string }> // expected to be { bar?: string }
复制代码
// one 
type IfEquals<X, Y> = [X] extends [Y] ? ([Y] extends [X] ? true : false) : false
type GetOptional<T> = {
  [P in keyof T as IfEquals<
    { [O in P]: T[P] },
    { [O in P]-?: T[P] }
  > extends true
    ? never
    : P]?: T[P]
}
复制代码
// two
type FilterOptionalKey<T, K extends keyof T> = T extends {
  [k in K]-?: T[k]
}
  ? never
  : K

type GetOptional<T> = {
  [K in keyof T as FilterOptionalKey<T, K>]: T[K]
}
复制代码

第二种方法基于以下思想

type hasA = {
  name?: string
  age: number
}
type A = {
  name: string
  age: number
}
type B = {
  name: string
}

type C = hasA extends B ? true : false //false
type D = A extends B ? true : false //true
复制代码

当属性可选时,为较大集合

type hasA = {
  name?: string
  age: number
}
type A = {
  name: string
  age: number
}

type C = hasA extends A ? true : false //false
type D = A extends hasA ? true : false //true
复制代码

Required Keys

实现高级util类型RequiredKeys<T>,该类型将所有必需的键都选择为一个并集。 example:

type Result = RequiredKeys<{ foo: number; bar?: string }>;
// expected to be “foo”
复制代码
type RequiredKeys<T> = keyof {
  [P in keyof T as T[P] extends Required<T>[P] ? P : never] : T[P]
}
复制代码

Optional Keys

实现高级util类型OptionalKeys<T>,该类型将所有可选键合并为一个并集。

// one
type OptionalKeys<T, K = keyof T> = K extends keyof T
  ? T extends Required<Pick<T, K>>
    ? never
    : K
  : never
复制代码

利用分配模式,逐一判断T 的key是否为可选

//two 
type DropUndefined<T> = T extends undefined ? never : T
type OptionalKeys<T> = DropUndefined<
  {
    [P in keyof T]: {} extends Pick<T, P> ? P : never
  }[keyof T]
>
复制代码

这里要注意的是可选属性在映射之后会存在一个undefined的union

type Map<T> = {
  [P in keyof T]: T[P]
}
/* 
  type C = {
    a: number;
    b?: string | undefined;
  }
*/
type C = Map<{ a: number; b?: string }>
复制代码

Capitalize Words

实现CapitalizeWords<T>,它将字符串中每个单词的第一个字母转换为大写,其余的保持原样。 example:

type capitalized = CapitalizeWords<'hello world, my friends'> // expected to be 'Hello World, My Friends'
复制代码
type FirstUppercase<T> = T extends `${infer L}${infer R}`
  ? `${Uppercase<L>}${R}`
  : T
type SpiltCode = ' ' | '.' | ','

type Recursion<S extends string> = S extends `${infer M}${infer L}${infer R}`
  ? M extends SpiltCode
    ? `${M}${Uppercase<L>}${Recursion<R>}`
    : `${M}${Recursion<`${L}${R}`>}`
  : S

type CapitalizeWords<T extends string> = Recursion<FirstUppercase<T>>
复制代码

CamelCase

实现 CamelCase<T> 将snake_case 字符串转换为camelCase。

example:

type camelCase1 = CamelCase<'hello_world_with_types'> // expected to be 'helloWorldWithTypes'
type camelCase2 = CamelCase<'HELLO_WORLD_WITH_TYPES'> // expected to be same as previous one
复制代码
type CamelCase<S extends string> = S extends `${infer L}_${infer R}${infer I}`
  ? `${Lowercase<L>}${Uppercase<R>}${CamelCase<I>}`
  : Lowercase<S>
复制代码

C-printf Parser

C语言中有一个函数:printf。这个函数允许我们打印带有格式化的内容。像这样

printf("The result is %d.", 42);
复制代码

这个问题要求您解析输入字符串并提取格式占位符,如%d和%f。例如,如果输入字符串是"the result is %d.",则解析的结果是一个元组['dec']

这是映射

type ControlsMap = {
  c: 'char',
  s: 'string',
  d: 'dec',
  o: 'oct',
  h: 'hex',
  f: 'float',
  p: 'pointer',
}
复制代码
type ParsePrintFormat<
  S extends string,
  T extends any[] = []
> = S extends `${string}%${infer M}${infer R}`
  ? M extends keyof ControlsMap
    ? ParsePrintFormat<R, [...T, ControlsMap[M]]>
    : ParsePrintFormat<R, [...T]>
  : T
复制代码

Vue Basic Props

这个挑战从Simple Vue开始,你应该先完成那个,然后根据它修改你的代码来开始这个挑战。

type Constructor<T> = T extends (...args: any) => infer R
  ? R
  : T extends new (...args: any) => infer R2
  ? R2
  : any

type PropType<T> = T extends Array<any>
  ? Constructor<T[number]>
  : Constructor<T>

type Prop<P> = {
  [K in keyof P]: P[K] extends {}
    ? 'type' extends keyof P[K]
      ? PropType<P[K]['type']>
      : PropType<P[K]>
    : PropType<P[K]>
}

type ComputedReturnType<T> = T extends Record<string, () => any>
  ? {
      [P in keyof T]: ReturnType<T[P]>
    }
  : never

interface Options<P, D, C, M> {
  props: P
  data: (this: Prop<P>) => D
  computed: C & ThisType<D>
  methods: M & ThisType<M & ComputedReturnType<C> & Prop<P>>
}
declare function VueBasicProps<P, D, C, M>(options: Options<P, D, C, M>): any
复制代码

这些要注意的是 class 在直接使用时,ts会自动推断为 typeof class

class ClassA {}

function test<T>(s: T) {
  return s
}
// const r: typeof ClassA
const r = test(ClassA)
复制代码

再结合这个结论

/**
 * 定义一个类
 */
class People {
  name: number;
  age: number;
  constructor() {}
}

// p1可以正常赋值
const p1: People = new People();
// 等号后面的People报错,类型“typeof People”缺少类型“People”中的以下属性: name, age
const p2: People = People;

// p3报错,类型 "People" 中缺少属性 "prototype",但类型 "typeof People" 中需要该属性
const p3: typeof People = new People();
// p4可以正常赋值
const p4: typeof People = People;
复制代码
  • 当把类直接作为类型时,该类型约束的是该类型必须是类的实例
  • 当把typeof 类作为类型时,约束的满足该类的类型

所以上边使用的是

T extends new (...args: any) => infer R2
复制代码

IsAny

编写一个实用程序类型IsAny<T>,它接受输入类型T。如果T是any,返回true,否则返回false

// one
type IsAny<T> =  unknown extends T
    ? [T] extends [string]
      ? true
      : false
    : false
复制代码
  1. unkonw 只能赋值给 unkonw 或者 any
  2. any 除了可以赋值给 unkonw 外还可以赋值给 其它值(除了never)

参考:www.typescriptlang.org/docs/handbo…

// two
ref: https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360
type IsAny<T> = 0 extends (1 & T) ? true : false;
复制代码

any 是所以类型的父类和子类(除了 never),所以当 1 & any 是为 any

Typed Get

实现ts版,类似 lodashget的功能 在此挑战中不需要访问数组。 example:

type Data = {
  foo: {
    bar: {
      value: 'foobar',
      count: 6,
    },
    included: true,
  },
  hello: 'world'
}
  
type A = Get<Data, 'hello'> // 'world'
type B = Get<Data, 'foo.bar.count'> // 6
type C = Get<Data, 'foo.bar'> // { value: 'foobar', count: 6 }
复制代码
type Get<T, K> = K extends `${infer L}.${infer R}`
  ? L extends keyof T
    ? Get<T[L], R>
    : never
  : K extends keyof T
  ? T[K]
  : never
复制代码

String to Number

将字符串文字转换为数字

type Map = {
  '0': []
  '1': [1]
  '2': [...Map['1'], 1]
  '3': [...Map['2'], 1]
  '4': [...Map['3'], 1]
  '5': [...Map['4'], 1]
  '6': [...Map['5'], 1]
  '7': [...Map['6'], 1]
  '8': [...Map['7'], 1]
  '9': [...Map['8'], 1]
}
type Make10Array<T extends any[]> = [
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T
]
type ToNumber<
  S extends string,
  L extends any[] = []
> = S extends `${infer F}${infer R}`
  ? ToNumber<R, [...Make10Array<L>, ...(F extends keyof Map ? Map[F] : never)]>
  : L['length']
复制代码

Tuple Filter

实现一个类型FilterOut<T, F>,它从元组T中过滤出给定的类型F

example:

type Filtered = FilterOut<[1, 2, null, 3], null> // [1, 2, 3]
复制代码
type FilterOut<T extends any[], F, Stash extends any[] = []> = T extends [
  infer L,
  ...infer R
]
  ? [L] extends [F]
    ? FilterOut<R, F, Stash>
    : FilterOut<R, F, [...Stash, L]>
  : Stash
复制代码

Tuple to Enum Object

example:

Enum<["macOS", "Windows", "Linux"]>
// -> { readonly MacOS: "macOS", readonly Windows: "Windows", readonly Linux: "Linux" }

Enum<["macOS", "Windows", "Linux"], true>
// -> { readonly MacOS: 0, readonly Windows: 1, readonly Linux: 2 }
复制代码
type IndexOf<T, P, S extends any[] = []> = T extends readonly [
  infer F,
  ...infer R
]
  ? P extends F
    ? S['length']
    : IndexOf<R, P, [...S, 1]>
  : S['length']

type Enum<T extends readonly string[], N extends boolean = false> = {
  readonly [P in T[number] as Capitalize<P>]: N extends true ? IndexOf<T, P> : P
}
复制代码

printf

example:

type FormatCase1 = Format<"%sabc"> // FormatCase1 : string => string
type FormatCase2 = Format<"%s%dabc"> // FormatCase2 : string => number => string
type FormatCase3 = Format<"sdabc"> // FormatCase3 :  string
type FormatCase4 = Format<"sd%abc"> // FormatCase4 :  string
复制代码
// 可以再拓展
type MapDict = {
  s: string
  d: number
}

type Format<T extends string> = T extends `${string}%${infer M}${infer R}`
  ? M extends keyof MapDict
    ? (x: MapDict[M]) => Format<R>
    : Format<R>
  : string
复制代码

Deep object to unique

创建一个类型,该类型接受一个对象,并使其及其中所有深度嵌套的对象唯一,同时保留所有对象的字符串和数字键,以及这些键上的所有属性的值。

example:

import { Equal } from "@type-challenges/utils"

type Foo = { foo: 2; bar: { 0: 1 }; baz: { 0: 1 } }

type UniqFoo = DeepObjectToUniq<Foo>

declare let foo: Foo
declare let uniqFoo: UniqFoo

uniqFoo = foo // ok
foo = uniqFoo // ok

type T0 = Equal<UniqFoo, Foo> // false
type T1 = UniqFoo["foo"] // 2
type T2 = Equal<UniqFoo["bar"], UniqFoo["baz"]> // false
type T3 = UniqFoo["bar"][0] // 1
type T4 = Equal<keyof Foo & string, keyof UniqFoo & string> // true
复制代码
type Merge<T> = {
  [P in keyof T]: T[P]
}
type DeepObjectToUniq<O extends object> = {
  [P in keyof O]: O[P] extends object
    ? DeepObjectToUniq<Merge<O[P] & { _xxx?: [O, P] }>>
    : O[P]
}
复制代码

Length of String 2

实现一个类型 LengthOfString<S> 来计算模板字符串的长度 该类型必须支持几百个字符长的字符串(通常字符串长度的递归计算受到TS中递归函数调用深度的限制,也就是说,它支持最多45个字符长的字符串)。

example:

type T0 = LengthOfString<"foo"> // 3
复制代码
type LengthOfString<S extends string, T extends any[] = []> = S extends ''
  ? T['length']
  : S extends `${infer L}${infer Q}${infer W}${infer E}${infer Y}${infer U}${infer I}${infer O}${infer P}${infer R}`
  ? LengthOfString<R, [...T, L, Q, W, E, Y, U, I, O, P]>
  : S extends `${infer L}${infer R}`
  ? LengthOfString<R, [...T, L]>
  : T['length']
复制代码

这里的实现思路很简单,每次递归多匹配一些好了,

Union to Tuple

实现一个类型,UnionToTuple,它将联合转换为元组。

因为联合类型是无序的,但是元组是有序的,因此在这个挑战中,输出任何顺序的元素都是可以的

example:

UnionToTuple<1>           // [1], and correct
UnionToTuple<'any' | 'a'> // ['any','a'], and correct
复制代码

它不应该是一个元组联合

UnionToTuple<'any' | 'a'> // ['a','any'], and correct
复制代码

联合可能会崩溃,这意味着某些类型可以吸收(或被其他类型吸收)并且无法阻止这种吸收。 请参阅以下示例:

Equal<UnionToTuple<any | 'a'>,       UnionToTuple<any>>         // will always be a true
Equal<UnionToTuple<unknown | 'a'>,   UnionToTuple<unknown>>     // will always be a true
Equal<UnionToTuple<never | 'a'>,     UnionToTuple<'a'>>         // will always be a true
Equal<UnionToTuple<'a' | 'a' | 'a'>, UnionToTuple<'a'>>         // will always be a true
复制代码

answers:

// https://github.com/type-challenges/type-challenges/issues/737
type UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends (
  x: infer U
) => any
  ? U
  : never

// get last Union: LastUnion<1|2> => 2
// ((x: A) => any) & ((x: B) => any) is overloaded function then Conditional types are inferred only from the last overload
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#type-inference-in-conditional-types
type LastUnion<T> = UnionToIntersection<
  T extends any ? (x: T) => any : never
> extends (x: infer L) => any
  ? L
  : never

type UnionToTuple<T, Last = LastUnion<T>> = [T] extends [never]
  ? []
  : [...UnionToTuple<Exclude<T, Last>>, Last]
复制代码

比较难理解的是 LastUnion

  1. 这里先把把每个联合类型的元素转换为了 一个函数

    (x: T) => any | (x: T) => any | (x: T) => any

  2. 利用分配模式,和函数的逆变性,把联合类型转换为 交集类型

    (x: T) => any & (x: T) => any & (x: T) => any

  3. 再利用多签名类型(例如函数重载)进行条件推断时,将只从最后一个签名进行推断

    参考文档: www.typescriptlang.org/docs/handbo…

String Join

创建一个字符串连接实用程序 example:

const hyphenJoiner = join('-')
const result = hyphenJoiner('a', 'b', 'c'); // = 'a-b-c'

// 或者
join('#')('a', 'b', 'c') // = 'a#b#c'

//当我们传递一个空分隔符(即")来连接时,应该将字符串原样连接起来,即
join('')('a', 'b', 'c') // = 'abc'

//当只传递一个项时,应该返回原始项(不添加任何分隔符)
join('-')('a') // = 'a'
复制代码
type JoinStr<
  J extends string,
  T extends string[],
  S extends string = ''
> = T extends [infer F, ...infer R]
  ? F extends string
    ? R extends string[]
      ? S extends ''
        ? JoinStr<J, R, `${F}`>
        : JoinStr<J, R, `${S}${J}${F}`>
      : `${S}${J}${F}`
    : S
  : S
declare function join<J extends string>(
  delimiter: J
): <T extends string[]>(...parts: T) => JoinStr<J, T>
复制代码

Deepick

实现一个类型DeepPick,它扩展了实用程序类型Pick

exapmle:


type obj = {
  name: 'hoge', 
  age: 20,
  friend: {
    name: 'fuga',
    age: 30,
    family: {
      name: 'baz',  
      age: 1 
    }
  }
}

type T1 = DeepPick<obj, 'name'>   // { name : 'hoge' }
type T2 = DeepPick<obj, 'name' | 'friend.name'>  // { name : 'hoge' } & { friend: { name: 'fuga' }}
type T3 = DeepPick<obj, 'name' | 'friend.name' |  'friend.family.name'>  // { name : 'hoge' } &  { friend: { name: 'fuga' }} & { friend: { family: { name: 'baz' }}}
复制代码
type UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends (
  x: infer R
) => any
  ? R
  : never

type GetValue<T, K extends string> = K extends keyof T
  ? Pick<T, K>
  : K extends `${infer L}.${infer R}`
  ? {
      [P in L]: P extends keyof T ? GetValue<T[P], R> : unknown
    }
  : unknown
type DeepPick<T, K extends string> = UnionToIntersection<GetValue<T, K>>
复制代码

Pinia

创建一个类型级别的函数,其类型与Pinia库类似。实际上你不需要实现函数,只需要添加类型。

这个函数只接收一个对象类型的参数。该对象包含4个属性

  • id - 只是一个字符串(必需)
  • state - 一个函数,返回一个对象作为store的状态(必需的)
  • getters - 一个具有类似于Vue的计算值或Vuex的getter方法的对象,详细信息如下(可选)
  • actions - 一个带有方法的对象,这些方法可以执行副作用和改变状态,详细信息如下(可选)

Getters

当你这样定义一个 store

const store = defineStore({
  // ...other required fields
  getters: {
    getSomething() {
      return 'xxx'
    }
  }
})
复制代码

你可以这样使用它

store.getSomething
复制代码

不可以这样使用

store.getSomething()  // error
复制代码

此外,getter 可以通过 this 访问 state 和/或其他 getter,但 state 是只读的。

Actions

当你像这样定义一个商店时

const store = defineStore({
  // ...other required fields
  actions: {
    doSideEffect() {
      this.xxx = 'xxx'
      return 'ok'
    }
  }
})
复制代码

可以直接调用它

const returnValue = store.doSideEffect()
复制代码

Actions可以返回任何值,也可以不返回任何值,并且它可以接收任意数量的不同类型的参数。参数类型和返回类型不能丢失,这意味着类型检查必须在调用端可用。

可以通过this获取和改变state,可以通过this获取getter但是是只读的

``

type GetGetters<T> = {
  [P in keyof T]: T[P] extends (...arg: any[]) => infer R ? R : never
}
type OptionsType<S, G, A> = {
  id: string
  state: () => Readonly<S>
  getters: G & ThisType<GetGetters<G> & Readonly<S>>
  actions: A & ThisType<A & S>
}
declare function defineStore<S, G, A>(
  store: OptionsType<S, G, A>
): A & Readonly<S> & GetGetters<G>
复制代码

vue 挑战是一样的,都是利用了函数泛型的推断

Camelize

实现 Camelize 将对象从snake_case 转换为camelCase

example:

Camelize<{
  some_prop: string, 
  prop: { another_prop: string },
  array: [{ snake_case: string }]
}>

// expected to be
// {
//   someProp: string, 
//   prop: { anotherProp: string },
//   array: [{ snakeCase: string }]
// }
复制代码
type TransformCamelcase<T> = T extends `${infer L}_${infer R}`
  ? `${L}${Capitalize<R>}`
  : T

type CamelikeTuple<T> = T extends [infer F, ...infer R]
  ? [Camelize<F>, ...CamelikeTuple<R>]
  : []

type CamelikeObject<T> = {
  [P in keyof T as TransformCamelcase<P>]: Camelize<T[P]>
}

type Camelize<T> = T extends Array<any>
  ? CamelikeTuple<T>
  : T extends {}
  ? CamelikeObject<T>
  : T
复制代码

Drop String

从字符串中删除指定的字符。

example:

type Butterfly = DropString<'foobar!', 'fb'> // 'ooar!'
复制代码
type StringToUnion<S extends string> = S extends `${infer F}${infer Rest}`
  ? F | StringToUnion<Rest>
  : never

type DropStringUnion<
  S extends string,
  R
> = S extends `${infer A}${infer B}${infer Rest}`
  ? A extends R
    ? B extends R
      ? `${DropStringUnion<Rest, R>}`
      : `${B}${DropStringUnion<Rest, R>}`
    : B extends R
    ? `${A}${DropStringUnion<Rest, R>}`
    : `${A}${B}${DropStringUnion<Rest, R>}`
  : S extends `${infer A}${infer Rest}`
  ? A extends R
    ? `${DropStringUnion<Rest, R>}`
    : `${A}${DropStringUnion<Rest, R>}`
  : S

type DropString<S extends string, R extends string> = DropStringUnion<
  S,
  StringToUnion<R>
>
复制代码

Split

example:

type result = Split<'Hi! How are you?', ' '>  // should be ['Hi!', 'How', 'are', 'you?']
复制代码
type Split<
  S extends string,
  SEP extends string
> = S extends `${infer L}${SEP}${infer R}`
  ? R extends ''
    ? [L]
    : [L, ...Split<R, SEP>]
  : S extends ''
  ? SEP extends ''
    ? []
    : [S]
  : S extends `${infer R}`
  ? [S]
  : string[]
复制代码

地狱

获取只读字段

实现泛型GetReadonlyKeys<T>,该GetReadonlyKeys<T>返回对象的只读键的并集。

example:

interface Todo {
  readonly title: string
  readonly description: string
  completed: boolean
}

type Keys = GetReadonlyKeys<Todo> // expected to be "title" | "description"
复制代码
type IsEqual<X, Y, A = true, B = false> = (<T>() => T extends X
  ? 1
  : 2) extends <T>() => T extends Y ? 1 : 2
  ? A
  : B
type GetReadonlyKeys<T> = {
  [P in keyof T]-?: IsEqual<
    { [O in P]: T[P] },
    { -readonly [O in P]: T[P] },
    never,
    P
  >
}[keyof T]
复制代码

剩下的地狱级别的个人觉得涉及到算法偏多一些,就没继续做下去,有兴趣同学的可以玩一玩

分类:
前端
分类:
前端