TypeScript 按任意类型过滤元组类型

标签 typescript types typescript-typings typescript-generics

我将如何通过在提供的元组中通过任意类型过滤提供的元组类型来生成新的元组类型?
示例( Playground ):

type Journey = ["don't", 'stop', 'believing'];

type ExcludeFromTuple<T extends unknown[], E> = ????;

type DepressingJourney = ExcludeFromTuple<Journey, "don't">; // type should be ['stop', 'believing']
请注意,解决方案不需要确保类型 E存在于类型 T之前,它只需要删除它,如果有的话。
尽管这里的示例很简单,但我有一个更复杂的用例,我希望能够通过我正在编写的库的使用者定义的任意类型过滤掉。
尽管 TypeScript 本身支持 exclude type ,它只适用于联合类型,我一直无法找到元组的等价物。
类似 ExcludeFromTuple 的类型对于生成其他实用程序类型非常有用。
type RemoveStringsFromTuple<T extends unknown[]> = ExcludeFromTuple<T, string>;
type RemoveNumbersFromTuple<T extends unknown[]> = ExcludeFromTuple<T, number>;
type RemoveNeversFromTuple<T extends unknown[]> = ExcludeFromTuple<T, never>;
type RemoveUndefinedsFromTuple<T extends unknown[]> = ExcludeFromTuple<T, undefined>;
我有一种感觉,该类型需要利用 TypeScript 2.8 的组合 conditional types ,
TypeScript 3.1 mapped types on tuples ,以及某种类型的递归类型魔术,但我一直无法弄清楚也找不到任何人。

最佳答案

TS 4.1+ 更新:
variadic tuple types在 TS 4.0 和 recursive conditional types 中引入TS4.1 中引入,现在可以写 ExcludeFromTuple更简单地为:

type ExcludeFromTuple<T extends readonly any[], E> =
    T extends [infer F, ...infer R] ? [F] extends [E ? ExcludeFromTuple<R, E> :
    [F, ...ExcludeFromTuple<R, E>] : []
您可以验证这是否按需要工作:
type DepressingJourney = ExcludeFromTuple<Journey, "don't">;
// type should be ['stop', 'believing']

type SlicedPi = ExcludeFromTuple<[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9], 1 | 9>
// type SlicedPi = [3, 4, 5, 2, 6, 5, 3, 5, 8, 7]
Playground link to code

TS-4.1 之前的答案:
哎呀,这才是真正需要的东西recursive conditional types TypeScript 尚不支持。如果你想使用它们,你可以这样做 at your own risk .通常我宁愿写一个应该是递归的类型,然后将它展开到一个固定的深度。所以,而不是 type F<X> = ...F<X>... , 我写 type F<X> = ...F0<X>...; type F0<X> = ...F1<X>...; .
为了写这个,我想对元组使用基本的“列表处理”类型,即 Cons<H, T>前置类型 H到元组 T ; Head<T>获取元组的第一个元素 T , 和 Tail<T>获取元组 T删除第一个元素。你可以这样定义:
type Cons<H, T> = T extends readonly any[] ? ((h: H, ...t: T) => void) extends ((...r: infer R) => void) ? R : never : never;
type Tail<T extends readonly any[]> = ((...t: T) => void) extends ((h: any, ...r: infer R) => void) ? R : never;
type Head<T extends readonly any[]> = T[0];
然后递归类型看起来像这样:
/* type ExcludeFromTupleRecursive<T extends readonly any[], E> = 
     T["length"] extends 0 ? [] : 
     ExcludeFromTupleRecursive<Tail<T>, E> extends infer X ? 
     Head<T> extends E ? X : Cons<Head<T>, X> : never; */
想法是:取元组的尾部T并执行 ExcludeFromTupleRecursive在上面。这就是递归。然后,对于结果,当且仅当它不匹配 E 时,您才应该添加元组的头部。 .
但这是非法循环,所以我像这样展开它:
type ExcludeFromTuple<T extends readonly any[], E> = T["length"] extends 0 ? [] : X0<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X0<T extends readonly any[], E> = T["length"] extends 0 ? [] : X1<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X1<T extends readonly any[], E> = T["length"] extends 0 ? [] : X2<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X2<T extends readonly any[], E> = T["length"] extends 0 ? [] : X3<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X3<T extends readonly any[], E> = T["length"] extends 0 ? [] : X4<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X4<T extends readonly any[], E> = T["length"] extends 0 ? [] : X5<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X5<T extends readonly any[], E> = T["length"] extends 0 ? [] : X6<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X6<T extends readonly any[], E> = T["length"] extends 0 ? [] : X7<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X7<T extends readonly any[], E> = T["length"] extends 0 ? [] : X8<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X8<T extends readonly any[], E> = T["length"] extends 0 ? [] : X9<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X9<T extends readonly any[], E> = T["length"] extends 0 ? [] : XA<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type XA<T extends readonly any[], E> = T["length"] extends 0 ? [] : XB<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type XB<T extends readonly any[], E> = T["length"] extends 0 ? [] : XC<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type XC<T extends readonly any[], E> = T["length"] extends 0 ? [] : XD<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type XD<T extends readonly any[], E> = T["length"] extends 0 ? [] : XE<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type XE<T extends readonly any[], E> = T; // bail out
还玩得开心吗?让我们看看它是否有效:
type DepressingJourney = ExcludeFromTuple<Journey, "don't">;
// type should be ['stop', 'believing']

type SlicedPi = ExcludeFromTuple<[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9], 1 | 9>
// type SlicedPi = [3, 4, 5, 2, 6, 5, 3, 5, 8, 7]
在我看来很好。
Link to code

关于TypeScript 按任意类型过滤元组类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58984164/

相关文章:

c++ - 为什么 "long int"与 "int"大小相同?这个修改器到底有没有用?

node.js - npm @types 包的类型 globalDevDependency 的等价物是什么?

Angular 和 PouchDBFind 插件

json - 带有 JSON 参数的 Typescript Rest API POST 调用

angular - 按月过滤列表

java - 您如何证明数组类型是引用类型?

sql - ORACLE中获取select语句中字段的数据类型

html - 重用具有不同输入的 Angular 组件

javascript - 尝试 - catch() 不捕获错误而是抛出错误

typescript - 将对象转换为 KV 值数组