如何在 TypeScript 3+ 中正确键入通用元组剩余参数?

Posted

技术标签:

【中文标题】如何在 TypeScript 3+ 中正确键入通用元组剩余参数?【英文标题】:How can I correctly type generic tuple rest parameters in TypeScript 3+? 【发布时间】:2019-01-13 19:32:04 【问题描述】:

在 TypeScript 3.0 之前,推断 N-arity 函数的类型涉及为已知的泛型参数创建任意数量的函数重载,然后使用类似 any 类型化的剩余参数来编写函数实现。 TypeScript 3.0 改变了这一点,因此泛型类型可以扩展 any[],它充当泛型元组。

此更改对我来说很有意义,并且对我自己的泛型参数来说效果很好,但我无法像在 3.0 之前那样让类型流经返回类型。

这是一个例子:

const takeToPromise = <T>(obs$: Observable<T>): Promise<T> => 
    obs$.pipe(take(1)).toPromise();

export function resolveObservables(): Promise<any[]>;
export function resolveObservables<T1>(o1: Observable<T1>): Promise<[T1]>;
export function resolveObservables<T1, T2>(o1: Observable<T1>, o2: Observable<T2>): Promise<[T1, T2]>;
export function resolveObservables<T1, T2, T3>(o1: Observable<T1>, o2: Observable<T2>, o3: Observable<T3>): Promise<[T1, T2, T3]>;
// Etc on the overloads
export function resolveObservables(...obs: (Observable<any>)[]): Promise<any[]> 
   return Promise.all(obs.map(takeToPromise));

假设我只有 3 个重载(最多 T3)。如果我通过将数组传播到函数调用中来使用resolveObservables,在 TS 3.0 中,我会得到一个错误:

预期有 0​​-3 个参数,但得到了 0 个或更多。

我可以将它与固定的 arity 调用一起使用,但一旦我尝试传播,就会出错。任何使这项工作的尝试都以某种方式失败了。例如:

export function resolveObservables<T, U extends Observable<T>[]>(...obs: U): Promise<T[]>;

将无法捕获任何类型(类型为)。

有什么方法可以修改这些类型,这样我就可以在不失去使用扩展参数的能力的情况下让类型流过?在我使用传播的情况下,我并不真正关心类型,如果我这样做了,我假设我可以只使用元组类型。但是根据我到目前为止的尝试,我可以使用 spread 但丢失类型,或者有类型但不能使用 spread。

我在这里错过了什么?

【问题讨论】:

【参考方案1】:

我们可以让它主要在 3.0 上工作,但要获得完整的元组映射支持,我们需要等待 3.1。

首先,您不需要额外的T 参数,只需使用U extends Observable&lt;any&gt;[] 的约束即可推断U 的正确元组类型

要提取结果元组类型,我们可以使用映射类型和条件类型从 observable 中提取结果类型。这是在 3.0 中几乎可以使用的部分。在 3.0 中,映射类型将破坏数组类型,因此在结果类型上只有索引操作仍然可以,所有方法都将是 never 类型。 3.1 将使元组和数组上的映射类型更符合预期(阅读PR 了解详细信息)。

export function resolveObservables
    <U extends Observable<any>[]>(...obs: U): Promise<ExtractObservableTypes<U>> 
    return null as any;



type ExtractObservableTypes<T> = 
    [P in keyof T] : T[P] extends Observable<infer U> ? U : never

let o!: Observable<number>
let o2!: Observable<string>


resolveObservables(o, o2).then(r => 
    r[0].toExponential // r[0] is number
    r[1].substring // r[1] is string
)

Playground link

【讨论】:

感谢您发布允许签名有效的答案。只是为了澄清,就像你在这里所做的那样,当使用ExtractObservableTypes&lt;U&gt; 时,我必须转换为any(即使使用Promise.all)。这是因为我们在 3.0 中没有正确的映射元组吗?基本上为什么 ExtractObservableTypes&lt;U&gt; 根据 TS 3 不能分配给 Promise&lt;any[]&gt;?再次感谢。 我还发现实际上这不允许解构。例如。 const r = await resolveObservables(foo$, bar$); const foo = r[0]; const bar = r[1]; 有效。但是const [foo, bar] = await resolveObservables(foo$, bar$); 不起作用 b/c 它不是数组类型。

以上是关于如何在 TypeScript 3+ 中正确键入通用元组剩余参数?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Typescript 在 mongoose 中正确键入对象 ID 数组

如何在 Typescript 中使用 redux-thunk 使用 ThunkAction 正确键入 thunk?

如何正确键入返回 TypeScript 中的类的函数?

如何使用 TypeScript 正确键入 Sequelize JOIN?

TypeScript:如何正确键入一个接受承诺并按原样返回该承诺的函数

如何使用 TypeScript 正确键入检查允许部分子树的嵌套记录?