TS试炼-6

摘要:

包含:ObjectEntries、Shift、Tuple to Nested Object(元组转嵌套对象)、Reverse(元组翻转)、Flip Arguments(函数参数翻转)、FlattenDepth(对元组扁平指定层级)、BEM(CSS流行命名约束)、InorderTraversal(二叉树索引遍历)、Flip(key和value交换)、Fibonacci(斐波那契序列)

1、ObjectEntries(Object.entries())

Github去练习

ts 复制代码
interface Model {
  name: string;
  age: number;
  locations: string[] | null;
}
type modelEntries = ObjectEntries<Model> // ['name', string] | ['age', number] | ['locations', string[] | null];

第一种方式

ts 复制代码
type RemoveUndefined<T> = [T] extends [undefined] ? T : Exclude<T, undefined>;
type ObjectEntries<T> = {
  [K in keyof T]-?: {} extends Pick<T, K> ? [K, RemoveUndefined<T[K]>] : [K, T[K]];
}[keyof T];

解析:
通过{} extends Pick<T, K>判断属性是否可选,根据是否可选处理要不要去除undefined

第二种方式

ts 复制代码
type ObjectEntries<T, U = Required<T>> = {
  [K in keyof U]: [K, U[K] extends never ? undefined : U[K]]
}[keyof U]

解析:
使用第二个变量,值为第一个参数的属性必选,必选后有以下情况
1.如果原属性是必选,则结果还为原类型(包括undefined)
2.如果原属性可选,则去除undefined,去除后如果没有类型则为never
如此:就执行要处理一下never的情况即可满足试题要求。

第三种方式

ts 复制代码
type ObjectEntries<T extends object, K extends keyof T = keyof T> = K extends K ? [K, [T[K]] extends [undefined] ? T[K] : Required<T>[K]] : never
type ObjectEntries<T extends object, U = Required<T>, K extends keyof U = keyof U> = K extends K ? [K, [U[K]] extends [never] ? undefined : U[K]] : never

解析:
先把key拿出来,key为联合类型,对联合类型执行extends相当于遍历。
仍然使用Required,只需要处理一下只为undefined的情况即可

2、Shift(删除元组第一个类型)

Github去练习

ts 复制代码
type Result = Shift<[3, 2, 1]> // [2, 1]

实现

ts 复制代码
type Shift<T extends unknown[]> = T extends [unknown, ...infer R] ? R : []

3、Tuple to Nested Object(元组转嵌套对象)

Github去练习

ts 复制代码
type a = TupleToNestedObject<['a'], string> // {a: string}
type b = TupleToNestedObject<['a', 'b'], number> // {a: {b: number}}
type c = TupleToNestedObject<[], boolean> // boolean. if the tuple is empty, just return the U type

实现

ts 复制代码
type TupleToNestedObject<T extends unknown[], U> = T extends [infer F extends PropertyKey, ...infer R] ? {
  [key in F]: TupleToNestedObject<R, U>
} : U

4、Reverse (元组翻转)

Github去练习

ts 复制代码
type a = Reverse<['a', 'b']> // ['b', 'a']
type b = Reverse<['a', 'b', 'c']> // ['c', 'b', 'a']

实现

ts 复制代码
type Reverse<T extends unknown[]> = T extends [infer F, ...infer R] ? [...Reverse<R>, F] : T
type Reverse<T extends unknown[]> = T extends [...infer R, infer L] ? [L, ...Reverse<R>] : T

5、Flip Arguments(函数参数翻转)

Github去练习

ts 复制代码
type Flipped = FlipArguments<(arg0: string, arg1: number, arg2: boolean) => void> 
// (arg0: boolean, arg1: number, arg2: string) => void

实现

ts 复制代码
type Reverse<T extends unknown[]> = T extends [infer F, ...infer R] ? [...Reverse<R>, F] : T
type FlipArguments<T extends Function> = T extends (...args: infer A) => infer R ? (...args: Reverse<A>) => R : never
// 也可以合并成一个
type FlipArguments<T extends Function, Args extends unknown[] = []> = T extends (
  ...args: infer A
) => infer V
  ? A extends [infer F, ...infer R]
    ? FlipArguments<(...args: R) => V, [F, ...Args]>
    : (...args: Args) => V
  : never;

解析:
主要是通过infer推断出参数,然后使用上面的元组翻转工具即可。

6、FlattenDepth(对元组扁平指定层级)

Github去练习

ts 复制代码
type a = FlattenDepth<[1, 2, [3, 4], [[[5]]]], 2> // [1, 2, 3, 4, [5]]. flattern 2 times
type b = FlattenDepth<[1, 2, [3, 4], [[[5]]]]> // [1, 2, 3, 4, [[5]]]. Depth defaults to be 1

实现

ts 复制代码
// 扁平化一层
type FlattenArray<T extends unknown[]> = T extends [infer F, ...infer R]
  ? F extends unknown[]
    ? [...F, ...FlattenArray<R>]
    : [F, ...FlattenArray<R>]
  : T;
// 判断是否已经完全扁平
type IsFlattenArray<T extends unknown[]> = T extends [infer F, ...infer R]
  ? F extends unknown[]
    ? false
    : IsFlattenArray<R>
  : true;
// 扁平指定层级
type FlattenDepth<T extends unknown[], D extends number = 1> = D extends 0  // 次数完毕
  ? T
  : IsFlattenArray<T> extends true  // 已经完全扁平,则直接结束,避免无限递归
    ? T
    : FlattenDepth<FlattenArray<T>, MinusOne<D>>; // 每次执行减一

解析:
通过判断层级为0或者已经完全扁平结束,未结束就将层级减一继续。MinusOne工具参考上一篇:MinusOne

另一种方案

ts 复制代码
// IsFlattenArray、FlattenArray同上
type FlattenDepth<
  T extends unknown[],
  D extends number = 1,
  U extends unknown[] = [],
> = U['length'] extends D
  ? T
  : IsFlattenArray<T> extends true
    ? T
    : FlattenDepth<FlattenArray<T>, D, [...U, 0]>;

解析:
每执行一次往元组里添加一个值,通过元组长度判断是否结束。(第一种占时间,第二种占空间)
注意:只要是使用递归处理的,都会有一个问题:递归次数达到限制,便会异常。

比较简洁的方案

ts 复制代码
// FlattenArray同上,是否完全扁平也用此方法判断
type FlattenDepth<
  T extends any[],
  D extends number = 1,
  U extends any[] = []
> = U['length'] extends D ? T : (
  FlattenArray<T> extends T ? T : (  // 如果扁平一层和不扁平相同,则直接返回
    FlattenDepth<FlattenArray<T>, D, [...U, 0]>
  )
)

7、BEM(CSS流行命名约束)

Github去练习

ts 复制代码
BEM<'btn', ['price'], []> -> 'btn__price'
BEM<'btn', ['price'], ['warning', 'success']> -> 'btn__price--warning' | 'btn__price--success'
BEM<'btn', [], ['small', 'medium', 'large']> -> 'btn--small' | 'btn--medium' | 'btn--large'

实现

ts 复制代码
type BEM<B extends string, E extends string[], M extends string[]> = `${B}${E extends [] ? '' : `__${E[number]}`}${M extends [] ? '' : `--${M[number]}`}`

8、InorderTraversal(二叉树索引遍历)

Github去练习

ts 复制代码
const tree1 = {
  val: 1,
  left: null,
  right: {
    val: 2,
    left: {
      val: 3,
      left: null,
      right: null,
    },
    right: null,
  },
} as const
// 结果:[1,3,2]

实现

ts 复制代码
interface TreeNode {
  val: number
  left: TreeNode | null
  right: TreeNode | null
}
type InorderTraversal<T extends TreeNode | null> = T extends TreeNode ? [...InorderTraversal<T['left']>, T['val'], ...InorderTraversal<T['right']>] : [];

9、Flip(key和value交换)

Github去练习

ts 复制代码
Flip<{ a: "x", b: "y", c: "z" }>; // {x: 'a', y: 'b', z: 'c'}
Flip<{ a: 1, b: 2, c: 3 }>; // {1: 'a', 2: 'b', 3: 'c'}
Flip<{ a: false, b: true }>; // {false: 'a', true: 'b'}

实现

ts 复制代码
type Flip<T extends Record<string, string | number | boolean>> = {
  [P in keyof T as `${T[P]}`]: P;
};

解释:
通过[P in keyof T]拿到key,用于新类型的值,再通过as来指定新类型的key类型。

10、Fibonacci(斐波那契序列)

Github去练习

ts 复制代码
type Result1 = Fibonacci<3> // 2
type Result2 = Fibonacci<8> // 21

实现

ts 复制代码
type Fibonacci<
  T extends number,
  No extends 0[] = [0, 0, 0],
  N_2 extends 0[] = [0],
  N_1 extends 0[] = [0],
> = T extends 1 | 2
  ? 1
  : T extends No['length']
    ? [...N_1, ...N_2]['length']
    : Fibonacci<T, [...No, 0], N_1, [...N_1, ...N_2]>;

解释:
使用数组和length实现
1、No用于存储当前是第几次,第一次和第二次比较特殊,结果都是1,可以特殊处理。T extends No['length']是从3开始判断。因此NO的初始值是一个长度为3的元组,,n1和n2都是长度为1的元组,这样初始值固定。
2、当T extends No['length']满足时,意味符合传入的参数,可以返回结果了,结果为n1.length+n2.length
3、不符合时,就需要递归了,第一个参数T不变,继续传递,No因为要执行下一次,所以把数组长度加一即可,下次的n1使用本次的n1和n2合并,下次的n2使用本次的n1。
注意:因ts元组最大长度限制,超出会提示元组过大,所以此方法最多支持的20,21时会报错。

支持更多的方法:🤣

ts 复制代码
// let arr = [1,1]
// for(let i = 1; i < 51; i++) arr.push(arr[i] + arr[i-1])  // 元组生成方法
// console.log(arr)

type FibonacciTuple = [
           0,          1,           1,           2,
           3,          5,           8,          13,
          21,         34,          55,          89,
         144,        233,         377,         610,
         987,       1597,        2584,        4181,
        6765,      10946,       17711,       28657,
       46368,      75025,      121393,      196418,
      317811,     514229,      832040,     1346269,
     2178309,    3524578,     5702887,     9227465,
    14930352,   24157817,    39088169,    63245986,
   102334155,  165580141,   267914296,   433494437,
   701408733, 1134903170,  1836311903,  2971215073,
  4807526976, 7778742049, 12586269025, 20365011074
]
type Fibonacci<T extends number> = FibonacciTuple[T];

评论

0条评论

logo

暂无内容,去看看其他的吧~