# bigfrontend的TypeScript问题

bigfrontend 大前端?

这么多题做下来,感觉用的最多的其实是条件类型+infer

# TypeScript内置类型

  • Partial
  • Required
  • Readonly
  • Record
  • Pick
  • Omit
  • Exclude
  • Extract
  • NonNullable
  • Parameters
  • ConstructorParameters
  • ReturnType
  • InstanceType
  • ThisParameterType
  • OmitThisParameter

# 技巧:数值转化成Tuple

# Repeat

先考虑这么一个问题:实现一个类型Repeat<T,C>,这个类型返回一个元组,元组的长度为C(意味着C是非负整数),元组的每一项都是T类型。

以下是测试用例:

type A = Repeat<number, 3> // [number, number, number]
type B = Repeat<string, 2> // [string, string]
type C = Repeat<1, 1> // [1]
type D = Repeat<0, 0> // []

以下是我们的答案:

type Repeat<T, C extends number,R extends T[] = []> = R['length'] extends C?R:Repeat<T,C,[T,...R]>

其中R是我们结果存放的元组。当元组的长度达到我们需要的长度C时(条件类型的true分支),我们返回R即可,当不满足时(其实就是长度不够),我们递归调用Repeat,并且传入的R参数比当前的R长度+1。

这样我们就把数值和元组关联起来了。

# ToNumber

请实现ToNumber<T>用来转换字符串为整数。

测试用例:

type A = ToNumber<'1'> // 1
type B = ToNumber<'40'> // 40
type C = ToNumber<'0'> // 0

答案:

type ToNumber<T extends string,U extends number[]=[]> = T extends `${U['length']}`?U['length']:ToNumber<T,[1,...U]>

这里还有一个技巧时通过模板字面量类型把数字类型转成字符串类型

# Add

请实现Add<A, B>计算正整数之和。

测试用例:

type A = Add<1, 2> // 3
type B = Add<0, 0> // 0

答案:

type Num2Arr<T extends number,U extends number[] = []> = U['length'] extends T?U:Num2Arr<T,[1,...U]>

type Add<A extends number, B extends number> = [...Num2Arr<A>,...Num2Arr<B>]['length']

# LargerThan

请实现LargerThan<A, B>

测试用例:

type A = LargerThan<0, 1> // false
type B = LargerThan<1, 0> // true
type C = LargerThan<10, 9> // true

答案:

type LargerThan<
  A extends number, 
  B extends number,
  AList extends number[] = [],
  BList extends number[] = [],
> = AList['length'] extends A?
      false:
      BList['length'] extends B?
        true: LargerThan<A,B,[1,...AList],[1,...BList]>

# SmallerThan

请实现SmallerThan<A, B>

测试用例:

type A = SmallerThan<0, 1> // true
type B = SmallerThan<1, 0> // false
type C = SmallerThan<10, 9> // false

答案:

type SmallerThan<
  A extends number, 
  B extends number,
  AList extends number[]=[],
  BList extends number[] = [] 
> = AList['length'] extends A?
      BList['length'] extends B?
        false:true
      :
      BList['length'] extends B?
        false:SmallerThan<A,B,[1,...AList],[1,...BList]>

type A = SmallerThan<0, 1> // true
type B = SmallerThan<1, 0> // false
type C = SmallerThan<10, 9> // false

# RepeatString

类似于String.prototype.repeat(),请实现RepeatString<T, C>

测试用例:

type A = RepeatString<'a', 3> // 'aaa'
type B = RepeatString<'a', 0> // ''

答案:

type TupleToString<T extends string[]> = T extends [infer A,...infer B]? B extends string[]? `${A & string}${TupleToString<B>}`:A :''
type Repeat2Tuple<T,C extends number,R extends T[] = []> = R['length'] extends C?R:Repeat2Tuple<T,C,[T,...R]>

type RepeatString<T extends string, C extends number> = TupleToString<Repeat2Tuple<T,C>>

# Tuple

# Push

请实现Push<T, I>

测试用例:

type A = Push<[1,2,3], 4> // [2,3]
type B = Push<[1], 2> // [1, 2]
type C = Push<[], string> // [string]

答案:

type Push<T extends any[], I> = [...T,I]

# Shift

请实现Shift<T>来去掉tuple的第一个元素

测试用例:

type A = Shift<[1,2,3]> // [2,3]
type B = Shift<[1]> // []
type C = Shift<[]> // []

答案:

type Shift<T extends any[]> = T extends [any,...infer U]?U:[]

# ReverseTuple

Implement ReverseTuple<T> to reverse a tuple type.

测试用例:

type A = ReverseTuple<[string, number, boolean]> // [boolean, number, string]
type B = ReverseTuple<[1,2,3]> // [3,2,1]
type C = ReverseTuple<[]> // []

答案:

type ReverseTuple<T extends any[]> = T extends [first:infer U,...rest:infer O]?[...ReverseTuple<O>,U]:T

# TupleToUnion

Given a tuple type, implement TupleToUnion<T> to get a union type from it.

测试用例:

type Foo = [string, number, boolean]

type Bar = TupleToUnion<Foo> // string | number | boolean

答案:

type TupleToUnion<T extends any[]> = T[number]

# LengthOfTuple

请实现LengthOfTuple<T>返回tuple type的长度。

测试用例:

type A = LengthOfTuple<['B', 'F', 'E']> // 3
type B = LengthOfTuple<[]> // 0

答案:

type LengthOfTuple<T extends any[]> = T extends {length:infer U}?U:never;

# FirstItem

请实现FirstItem<T>来返回tuple type的第一个type。

测试用例:

type A = FirstItem<[string, number, boolean]> // string
type B = FirstItem<['B', 'F', 'E']> // 'B'

答案:

type FirstItem<T extends any[]> = T extends [infer U,...any[]]?U:never;

# LastItem

请实现LastItem<T>用以返回tuple的最后一个type。

测试用例:

type A = LastItem<[string, number, boolean]> // boolean
type B = LastItem<['B', 'F', 'E']> // 'E'
type C = LastItem<[]> // never

答案:

type LastItem<T extends any[]> = T extends [...any[],infer U]?U:never;

# String

字符串相关的基本需要TypeScript版本最少4.1

# FirstChar

请实现FirstChar<T>返回头文字type。

测试用例:

type A = FirstChar<'BFE'> // 'B'
type B = FirstChar<'dev'> // 'd'
type C = FirstChar<''> // never

答案:

type FirstChar<T extends string> = T extends `${infer U}${infer O}`?U:never

# LastChar

类似于FirstChar<T>,请实现LastChar<T>

测试用例:

type A = LastChar<'BFE'> // 'E'
type B = LastChar<'dev'> // 'v'
type C = LastChar<''> // never

答案:

type LastChar<T extends string> = T extends `${infer U}${infer O}`?O extends ''?U:LastChar<O> :never

# StringToTuple

请实现StringToTuple<T>将字符串拆散为tuple。

测试用例:

type A = StringToTuple<'BFE.dev'> // ['B', 'F', 'E', '.', 'd', 'e','v']
type B = StringToTuple<''> // []

答案:

type StringToTuple<T extends string,U extends string[] = []> = T extends `${infer A}${infer B}`? StringToTuple<B,[...U,A]>:U

# LengthOfString

请实现LengthOfString<T>用以返回字符串长度。

测试用例:

type A = LengthOfString<'BFE.dev'> // 7
type B = LengthOfString<''> // 0

答案:

type LengthOfString<T extends string,U extends string[] = []> = T extends `${infer A}${infer B}`?LengthOfString<B,[A,...U]>:U['length']

类似于将数字转换为Tuple,这里通过递归和infer不断拆解字符串,转换为Tuple

# Trim

正如String.prototype.trim(),请实现Trim<T>

测试用例:

type A = Trim<'    BFE.dev'> // 'BFE'
type B = Trim<' BFE. dev  '> // 'BFE. dev'
type C = Trim<'  BFE .   dev  '> // 'BFE .   dev'

答案:

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

# ReplaceAll

Just like String.prototype.replaceAll(),please implement ReplaceAll<S, F, T>.

type ReplaceAll<
  S extends string, 
  F extends string, 
  T extends string,
  P extends string = ''
  > = F extends ''? S:
      S extends `${infer A}${F}${infer B}`? ReplaceAll<B,F,T,`${P}${A}${T}`>:`${P}${S}`

# is

# IsAny 🤓

测试用例:

type A = IsAny<string> // false
type B = IsAny<any> // true
type C = IsAny<unknown> // false
type D = IsAny<never> // false

答案:

type IsAny<T> = 0 extends (1&T)?true:false

需要知道any与除never之外的其他类型求交叉类型,都是any。

# IsNever 🤓

测试用例:

type A = IsNever<never> // true
type B = IsNever<string> // false
type C = IsNever<undefined> // false

答案:

type IsNever<T> = [T] extends [never]?true:false

注意不能写成type IsNever<T> = T extends never?true:false,当T为never时,结果时never。

# IsEmptyType 🤓

Implement IsEmptyType<T> to check if T is empty type {}.

测试用例:

type A = IsEmptyType<string> // false
type B = IsEmptyType<{a: 3}> // false
type C = IsEmptyType<{}> // true
type D = IsEmptyType<any> // false
type E = IsEmptyType<object> // false
type F = IsEmptyType<Object> // false

答案:

// 先判断是否是对象
type IsEmptyType<T> = T extends Record<string,1>? 
  // 空对象 keyof操作是 never
  [keyof T] extends [never]?true:false :
  false;

#

# Flat 🤓

Implement Flat<T> to flatten a tuple type.

测试用例:

type A = Flat<[1,2,3]> // [1,2,3]
type B = Flat<[1,[2,3], [4,[5,[6]]]]> // [1,2,3,4,5,6]
type C = Flat<[]> // []

答案:

type Flat<T extends any[]> = 
  T extends [infer F,...infer R]? 
    // F有可能还是数组类型 也可能不是
    F extends any[]?
      [...Flat<F>,...Flat<R>]:
      [F,...Flat<R>]
    : T

# UnwrapPromise

Implement UnwrapPromise<T> to return the resolved type of a promise.

测试用例:

type A = UnwrapPromise<Promise<string>> // string
type B = UnwrapPromise<Promise<null>> // null
type C = UnwrapPromise<null> // Error

答案:

type UnwrapPromise<T> = T extends Promise<infer U>?U:Error;

# UnionToIntersection

请实现UnionToIntersection<T>用以从Union得到Intersection。

测试用例:

type A = UnionToIntersection<{a: string} | {b: string} | {c: string}> 
// {a: string} & {b: string} & {c: string}

答案:

type UnionToIntersection<T> = (T extends any? (arg:T)=>any:never) extends (arg:infer R)=>void?R:never;

参考文章

第一个条件类型是分布式条件类型,其实起到了把联合类型拆开的目的,第二个条件类型是非分布式条件类型,同时由于函数的类型兼容性,对于函数参数是逆变,所以上面的R是各个被联合的类型的子类型。

# Filter<T, A>

请实现Filter<T, A>,返回T中能够assign给A的type所组成的新tuple。

测试用例:

type A = Filter<[1,'BFE', 2, true, 'dev'], number> // [1, 2]
type B = Filter<[1,'BFE', 2, true, 'dev'], string> // ['BFE', 'dev']
type C = Filter<[1,'BFE', 2, any, 'dev'], string> // ['BFE', any, 'dev']

答案:

// 拆分数组,一个元素一个元素处理
type Filter<T extends any[], A> = T extends [infer F,...infer R]?
    // 判断F是否可以赋给A
    // 这里要用非分布式条件类型 考虑到第三个case any的情况 如果是分布式条件类型的话 true和false分支都会运行
    [F] extends [A]?
        [F,...Filter<R,A>]:
        Filter<R,A>
    // 空数组了
    :[]

# Equal

Implement Equal<A, B> to check if two types are identical.

测试用例:

Equal<any, any> // true
Equal<any, 1> // false
Equal<never, never> // true
Equal<'BFE', 'BFE'> // true
Equal<'BFE', string> // false
Equal<1 | 2, 2 | 1> // true
Equal<{a : number}, {a: number}> // true

答案:

type isAny<T> = 0 extends (1&T)?true:false;
// 前半部分都是处理any的情况
type Equal<A, B> = isAny<A> extends true?
    isAny<B> extends true?
        true:false
    : isAny<B> extends true ? false:
        // 到这里没有any了 那么就是可以互相作为子类
        // 要用非分布式类型 分布式类型处理不了never
        [A] extends [B] ?
        [B] extends [A]
        ? true: false
        :false

# FindIndex

正如Array.prototype.findIndex(), 请实现FindIndex<T, E>

测试用例:

type A = [any, never, 1, '2', true]
type B = FindIndex<A, 1> // 2
type C = FindIndex<A, 3> // never

答案:

type FindIndex<T extends any[], E,H extends any[]=[]> = T extends [infer F,...infer O]?
  Equal<F,E> extends true? H['length']: FindIndex<O,E,[1,...H]>
  :never;

# UndefinedToNull

type UndefinedToNull<T> = 
T extends Record<keyof any,any> ? 
  {
    [K in keyof T]:UndefinedToNull<T[K]>
  }: T extends undefined? null:T

# MapStringUnionToObjectUnion

type MapStringUnionToObjectUnion<T> = 
T extends any? {
  value:T
} :never

# Diff

type DiffKeys<
  A extends Record<string, any>,
  B extends Record<string, any>,
  KS = (keyof A) | (keyof B)
> = 
KS extends any?
  KS extends (keyof A)?
    KS extends (keyof B)?
      never:KS
    : 
    KS extends (keyof B)?
      KS:never

  :never

# ObjectPaths

type ObjectPaths<O extends Record<string, any>, Path extends string = '',KS = keyof O > = 
  KS extends keyof O?
    O[KS] extends Record<string,any>?
      ObjectPaths<O[KS],Path extends ''? KS: `${Path}.${KS&string}`  >
      : Path extends ''? KS: `${Path}.${KS&string}`
    :never

# Abs

type Abs<N extends number> = `${N}` extends `-${infer N1 extends number}`?N1:N

# StringToNumber

type StringToNumber<S extends string> = S extends `${infer N extends number}`?N:never

# Split

type Split<S extends string, D extends string,R extends string[] = []> = 
  S extends `${infer A}${D}${infer B}`?
    Split<B,D,[...R,A]>:[...R,S]

# Capitalize

type MyCapitalize<T extends string> = T extends `${infer A}${infer B}`? `${Uppercase<A>}${B}`:T

# CamelCase

type CamelCase<S extends string> = S extends `${infer A}_${infer B}`? `${Capitalize<A>}${CamelCase<B>}`:Capitalize<S>

# SnakeCase

type SnakeCase<S extends string,P extends string = ''> = 
  S extends `${infer F}${infer R}`?
    F extends Uppercase<F>?
      SnakeCase<R, `${P}${P extends ''?'':'_' }${Lowercase<F>}`>
      :SnakeCase<R,`${P}${F}`>
    :`${P}${S}`

# Slice

type Slice<
  A extends any[], 
  S extends number = 0, 
  E extends number = A['length'],
  P extends any[] = [],
  R extends any[] = [],
  I extends boolean = false,
> = 
P['length'] extends E?
  R:
  A extends [infer F,...infer G]?
    I extends false?
      P['length'] extends S?
        Slice<G,S,E,[...P,F],[...R,F],true>
        :Slice<G,S,E,[...P,F],R,false>
      :Slice<G,S,E,[...P,F],[...R,F],true>

    :R

# Prefix

type Prefix<T extends Record<string, any>, P extends string> 
  = {
    [K in keyof T as `${P}_${K&string}`]:T[K]
  }