web-dev-qa-db-ja.com

TypeScriptで関数パラメータを推測する

タイプセーフなマップ関数(以下の関数ではありません)を作成しようとしていますが、関数パラメーターを正しく推測するために立ち往生しています。

    export type Mapper<U extends Unmapped> = {
      mapped: Mapped<U>
    };

    export type Unmapped = {
      [name: string]: (...args: any[]) => any
    };

    export type Mapped<U extends Unmapped> = {
      [N in keyof U]: (...args: any[]) => Promise<any>
    };

    const map = <U extends Unmapped>(unmapped: U): Mapper<U> => ({
      mapped: Object.entries(unmapped).reduce(
        (previous, [key, value]) => ({
          ...previous,
          [key]: (...args: any[]) => new Promise((resolve) => resolve(value(...args)))
        }),
        {}
      ) as Mapped<U>
    });

    const mapped = map({ test: (test: number) => test });

    mapped.mapped.test('oh no');

TypeScriptにそれらを推測させることは可能ですか?現在、mappedオブジェクト内の関数はすべてのパラメーターを受け入れますが、マップされていないオブジェクトで定義されたパラメーターのみを受け取る必要があります。関数名は正しく推測されます。

8
houfio

マップされた型の署名として(...args: any[]) => Promise<any>を使用すると、すべてのパラメーター型情報が失われ、型情報が返されます。条件付き型を使用すると、実行したいことに対する不完全な解決策を実現で​​きます。制限について説明します ここ

このソリューションでは、指定された数のパラメーターを使用して各関数を個別に処理する条件付きタイプを作成する必要があります。以下のソリューションは、最大10個のパラメーターで機能します(ほとんどの実用的なケースでは十分です)

export type Mapper<U extends Unmapped> = {
    mapped: Mapped<U>
};

export type Unmapped = {
    [name: string]: (...args: any[]) => any
};

type IsValidArg<T> = T extends object ? keyof T extends never ? false : true : true;

type Promisified<T extends Function> =
    T extends (...args: any[]) => Promise<any> ? T : (
        T extends (a: infer A, b: infer B, c: infer C, d: infer D, e: infer E, f: infer F, g: infer G, h: infer H, i: infer I, j: infer J) => infer R ? (
            IsValidArg<J> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J) => Promise<R> :
            IsValidArg<I> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) => Promise<R> :
            IsValidArg<H> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) => Promise<R> :
            IsValidArg<G> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => Promise<R> :
            IsValidArg<F> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F) => Promise<R> :
            IsValidArg<E> extends true ? (a: A, b: B, c: C, d: D, e: E) => Promise<R> :
            IsValidArg<D> extends true ? (a: A, b: B, c: C, d: D) => Promise<R> :
            IsValidArg<C> extends true ? (a: A, b: B, c: C) => Promise<R> :
            IsValidArg<B> extends true ? (a: A, b: B) => Promise<R> :
            IsValidArg<A> extends true ? (a: A) => Promise<R> :
            () => Promise<R>
        ) : never
    );

export type Mapped<U extends Unmapped> = {
    [N in keyof U]: Promisified<U[N]>
}

const map = <U extends Unmapped>(unmapped: U): Mapper<U> => ({
    mapped: Object.entries(unmapped).reduce(
        (previous, [key, value]) => ({
            ...previous,
            [key]: (...args: any[]) => new Promise((resolve) => resolve(value(...args)))
        }),
        {}
    ) as Mapped<U>
});

const mapped = map({ test: (test: number) => test });

mapped.mapped.test('oh no'); 

ParametersおよびReturnTypeジェネリック型を使用して、関数の特定のパラメーターと戻り値の型を取得できます。

type Promisified<T extends (...args: any[]) => any> = (...args: Parameters<T>) => Promise<ReturnType<T>>;

export type Mapped<U extends Unmapped> = {
    [N in keyof U]: Promisified<U[N]>
}