具有默认值的多个通用约束相互依赖

Multiple generic constraints with defaults relying on each other

这是我的 Typescript interface/class 结构:

interface IBaseOptions {
    homeUrl: string;
}

abstract class BaseApp<TOptions extends IBaseOptions = IBaseOptions> {
    constructor(
        private options: TOptions
    ) {
        // does nothing
    }
}

// -----------------------------

interface ICalls {
    getValue: () => number;
}

interface IOptions<TCalls extends ICalls = ICalls> extends IBaseOptions {
    calls: TCalls;
}

class App<TOptions extends IOptions<TCalls> = IOptions<TCalls>, TCalls extends ICalls = ICalls> extends BaseApp<TOptions> {
                                   -------- (ts2744 error here)
    constructor(options: TOptions) {
        super(options);
    }
}

class SubApp extends App {
    // whatever implementation
}

我想提供默认值,这样我就不必为我的选项和调用提供具体类型。我定义类型的方式导致编译错误(ts2744 错误)。

我还想避免交换我的泛型类型(带有约束和默认值),以便我将第一个泛型类型保留为选项并调用第二个。

有什么方法可以先定义带有约束的泛型类型,然后设置它们的默认值吗?

你可以查看这个Playground Link

最明显的解决方法,即交换类型参数的顺序,对您不起作用。所以我们不得不求助于不太明显的修复。这里的一般想法是:如果您不能将默认值设置为您想要的值,请将其设置为虚拟值,然后在稍后使用该类型时,检查虚拟值并使用您最初想要的默认值。所以 Foo<T=Default<U>, U=X> ... T 变成了 Foo<V=DefaultSigil, U=X> ... V extends DefaultSigil ? Default<U> : V.

这是一种方法:

type OrDefault<T> = [T] extends [never] ? IOptions<ICalls> : T;

class App<O extends IOptions<C> = never, C extends ICalls = ICalls>
    extends BaseApp<OrDefault<O>> {
    constructor(options: OrDefault<O>) {
        super(options);
    }
}

在这种情况下,我们使用 never 作为虚拟默认值;除非有人为 O 手动指定 never,否则用作虚拟值是一个安全的值。然后 OrDefault 检查它的参数是否是 never ,如果是 returns IOptions<ICalls>

是的,您注意到 OrDefault 的支票是 [T] extends [never] ? ... : ... 而不是 T extends never ? ... : ...。我这样做的原因是为了避免 distributing 跨越 T 的条件类型。由于 T 是 "naked type parameter",当您像 T extends never ? ... : ... 一样检查它时,编译器将尝试将 T 解释为联合,将联合拆分为成员,执行条件,然后然后将结果合并回一个联合体。如果您为 T 传入 never,这将被视为 "empty union",结果将始终为 never。我们不希望 OrDefault<never> 成为 never,因此我们不希望分配条件类型。最简单的解决方法是防止检查成为 "naked" 类型参数,呃,"clothing" 它在一个元组中。 [T] 未分解为并集成分,因此 OrDefault<never> 将根据需要变为 IOptions<ICalls>

之后我们不再使用 O,而是使用 OrDefault<O>。您应该能够验证这是否按照您想要的方式工作。它笨重且令人费解,但它确实有效。


好的,希望对您有所帮助;祝你好运!

Playground link to code