首页 > 解决方案 > TypeScript 可变参数元组类型推断问题,用于运行一系列“结果”返回函数的函数

问题描述

(TypeScript 游乐场中的完整代码:链接。)

我有一个Result<T, E>用于计算的类型,它要么以 type 的值成功,要么以 typeT的错误失败E

type Result<T, E> =
    | {ok: true, value: T}
    | {ok: false, value: E};

我想编写一个辅助函数,它可以Result按顺序运行 -returning 函数列表。如果任何函数失败,则停止处理并返回错误。如果全部成功,则返回成功值列表。

以下是专门为长度为 3 的列表实现该功能的方法:

function sequenceFixed<T1, T2, T3, E>(fns: [() => Result<T1, E>, () => Result<T2, E>, () => Result<T3, E>]): Result<[T1, T2, T3], E> {
    const [f1, f2, f3] = fns;
    const r1 = f1();
    if (!r1.ok) return r1;
    const r2 = f2();
    if (!r2.ok) return r2;
    const r3 = f3();
    if (!r3.ok) return r3;
    return {ok: true, value: [r1.value, r2.value, r3.value]};
}

我想使用 TypeScript 4.0 的可变元组类型来使它适用于任何长度的列表。(函数体本身不需要类型检查,只需要调用站点。)

这是我的两次尝试:

declare function sequenceGeneric1<T extends Array<unknown>, E>(
    fns: Readonly<{[P in keyof T]: () => Result<T[P], E>}>,
): Result<[...T], E>;

declare function sequenceGeneric2<Fns extends Array<unknown>, E>(
    fns: Fns,
): Result<{[P in keyof Fns]: ExtractResultSuccessType<Fns[P]>}, E>;

type ExtractResultSuccessType<Fn> = Fn extends () => Result<infer T, unknown> ? T : unknown;

如果我明确传递类型参数,则正确调用这些函数类型检查,但我不知道如何让 TypeScript 推断类型参数。

declare function f1(): Result<string, string>;
declare function f2(): Result<boolean, string>;
declare function f3(): Result<number, string>;

function checkFixed() {
    return sequenceFixed([f1, f2, f3]);
}
function checkGeneric1() {
    return sequenceGeneric1([f1, f2, f3]);
}

function checkGeneric2() {
    return sequenceGeneric2([f1, f2, f3]);
}

function check() {
    // ok
    const c1: Result<[string, boolean, number], string> = checkFixed();

    // ERROR: Type 'Result<(string | number | boolean)[], unknown>' is not assignable to
    //        type 'Result<[string, boolean, number], string>'.
    const c2: Result<[string, boolean, number], string> = checkGeneric1();
    
    // ERROR: Type 'Result<(string | number | boolean)[], unknown>' is not assignable to
    //        type 'Result<[string, boolean, number], string>'.
    const c3: Result<[string, boolean, number], string> = checkGeneric2();
}

function checkGeneric1WithAnnontation(): Result<[string, boolean, number], string> {
    return sequenceGeneric1<[string, boolean, number], string>([f1, f2, f3]);
}

标签: typescripttypescript-genericsvariadic-tuple-types

解决方案


在这种情况下,走你的路线肯定更容易sequenceGeneric2,你有一个与传入参数的类型直接对应的泛型参数;毕竟,编译器T从 type 的值推断类型T比编译器T从 type 的值推断类型更直接Readonly<{[K in keyof T]: SomeFunction<T[K]>}>

在您的情况下,您正在朝着正确的方向前进Fns,但是您仍然期望编译器能够推断出E,这只是不适合这样做,原因与您无法很好T地推断出元组的原因相同sequenceGeneric1


还有一件事:如果将数组文字传递给函数,编译器很有可能会为其推断出无序数组类型而不是元组类型。有一些方法可以向编译器提示您希望推断出元组类型。一种方法是使用可变元组表示法......所以fns: Fns你可以写而不是写fns: readonly [...Fns]。就可以是什么类型而言,这并没有太大的区别fns,但它确实导致编译器更喜欢 tuple-typed Fns


所以,继续:让我们只有一个泛型类型参数,Fns然后让编译器计算输出中的类型T类型E

declare function sequenceGeneric<Fns extends Array<() => Result<any, any>>>(
    fns: readonly [...Fns],
): Result<
  { [P in keyof Fns]: ExtractResultSuccessType<Fns[P]> },
  ExtractResultErrorType<Fns[keyof Fns]>
>;

在哪里

type ExtractResultSuccessType<Fn> = Fn extends () => Result<infer T, any> ? T : never;
type ExtractResultErrorType<Fn> = Fn extends () => Result<any, infer E> ? E : never;

我已将unknown推断类型更改为,any因为编译器更容易验证它。 unknown是一种真正的顶级类型,有时以您并不真正想要的方式严格遵守协变/逆变。


让我们看看它是否有效:

function checkGeneric() {
    return sequenceGeneric([f1, f2, f3]);
}
// function checkGeneric(): Result<[string, boolean, number], string>

看起来不错!


Playground 代码链接


推荐阅读