打字稿:如何将"n-tuple of getter functions"型转换为"n-tuple of returned values"型?



假设你有一个函数,它有一个 getter 函数数组作为输入,并返回一个具有相应返回值的数组(长度相同(。

下面的示例中,类型定义type ValueTupleOf<...> = ...的外观如何?

function getValues<A extends (() => any)[]>(getters: A): ValueTupleOf<A> {
  return getters.map(getter => getter())
}

编辑:当然,我可以做以下事情,但我希望有更好的解决方案:

function getValues<A1>(getters: [() => A1]): [A1]
function getValues<A1, A2>(getters: [() => A1, () => A2]): [A1, A2]
function getValues<A1, A2, A3>(getters: [() => A1, () => A2, () => A3]): [A1, A2, A3]
// ... etc
function getValues(getters: any): any {
  return getters.map(getter => getter())
}

你去...(不言自明(另外,演示

type ReturnTypes<T> = {
  [K in keyof T]: T[K] extends (...args: any[]) => infer R ? R : never
}
type Getter<T = {}> = () => T;
function getValues<Getters extends Array<Getter>>(getters: Getters) {
  return <ReturnTypes<Getters>>getters.map(getter => getter())
}
let values = getValues(<[() => "a1", () => "a2"]>[() => "a1", () => "a2"])
/* type of values is ["a1", "a2"] (hover to check)
In latest TS version you can use <const> to make tuples,
instead of writing things twice like I did here
*/
type foo = ReturnTypes<[() => "a1", () => "a2"]>;
// foo is ["a1", "a2"]
getValues(<[(x: string) => "a1"]>[(x: string) => "a1"])
// Also type-safe ^ gives error 

简答

坚持超载。它们最初可能看起来很多工作,但替代方案甚至更糟。

长答案

TypeScript 没有可变参数类型,所以为了保持元组的顺序,你必须使用一个技巧。但是,这种技巧很丑陋并且有局限性。

import { Reverse } from 'typepark';
type Thunk<T> = () => T;
type ReturnTypesOf<T extends Thunk<any>[], Length extends number = T['length'], Queue extends any[] = [], Index extends number = Queue['length']> = {
  done: Queue;
  next: ReturnTypesOf<T, Length, Prepend<ReturnType<T[Index]>, Queue>>;
}[Yield<Queue, Length>];
type Yield<Queue extends any[], Length extends number> =
  Queue['length'] extends Length
    ? 'done'
    : 'next';
type Prepend<T, U extends any[]> =
  ((head: T, ...tail: U) => void) extends ((...all: infer V) => void)
    ? V
    : [];
type T1 = ReturnTypesOf<[() => 1, () => 'foo']>; // ["foo", 1]
type T2 = Reverse<T1>; // [1, "foo"]

如您所见,它可以解决问题,但是:

  • 超级难理解
  • 以相反的顺序产生结果
  • 当反转相反的顺序时,可能会陷入 类型实例化太深,可能是无限的。 的情况。

@jcalz的答案

jcalz 在评论中提到可以使用映射的元组类型。如果你不介意一个接一个地传递参数(而不是传递数组(,你可以这样做:

type AnyFunction = () => any;
/**
 * Mapped type, but for tuples.
 */
type ReturnTypesOf<T extends AnyFunction[]> = {
  [K in keyof T]: T[K] extends AnyFunction
    ? ReturnType<T[K]>
    : never
};
function getValues<T extends AnyFunction[]>(...functions: T) {
  return functions.map(callback => callback()) as ReturnTypesOf<T>;
}
const values = getValues(
  () => "foo",
  () => 42
)

相关内容