首页 > 解决方案 > 泛型类的构造函数中可选字段的默认值

问题描述

我试图弄清楚是否有可能(仅使用类型声明)以某种方式适合可选字段的具体默认值——在泛型类的上下文中,这样这个默认值的类型仍然与另一个相关联必填项目

    type Handler<T> = (msg: T) => boolean;

    type Transformer<T> = (msg: SimpleMsg) => T;

    class Consumer<T> {

        handler: Handler<T>;
        transformer: Transformer<T>;

        constructor(handler: Handler<T>, transformer?: Transformer<T>) {
            this.handler = handler;
            this.transformer = transformer || defaultTransformer
        }

    }

默认转换器可以是这样的(只是传递值):

    const defaultTransformer = (msg: SimpleMsg) => {
        console.log('BoringTranfomer! ' + JSON.stringify(msg));
        return msg;
    } 

目前它(正确地)警告我T可以用与无关的类型实例化SimpleMsg

因此,我会 - 以某种方式想根据变压器的(类型?)定义处理程序的类型(反之亦然?) - 并强制该类型SimpleMsg以防变压器未定义(即未提供)

我知道可以使用工厂方法或其他方法来解决它,我将处理程序明确定义为Handler<SimpleMsg>,但我真的想知道它是否可以通过类型和单个入口点解决

谢谢!

标签: typescriptgenericstypescript-genericsconditional-types

解决方案


ATransformer返回T,所以我们不可能在不知道是什么的情况下创建默认值T。但是你在这里有正确的想法:

并强制该类型为 SimpleMsg 以防变压器未定义(即未提供)

构造函数重载

我们可以通过constructor使用多个参数类型重载函数来做到这一点。我们允许任何匹配的handlertransformer函数对同一个TOR 只是 ahandler需要 a SimpleMsg。Typescript 在第二种情况下无法推断出的类型T并且会返回Consumer<unknown>,所以我们必须将T类的默认值设置为SimpleMsg

构造函数的主体只知道实现签名中的类型,这与您之前的签名相同。所以我们确实需要断言defaultTransformer是正确的类型。

(我重命名TransformerMsgTransformer以避免出现重复的类型错误)

class Consumer<T = SimpleMsg> {

    handler: Handler<T>;
    transformer: MsgTransformer<T>;

    // if a transformer is provided, it must match the handler    
    constructor(handler: Handler<T>, transformer: MsgTransformer<T>)
    // if no transformer is provided, then the handler must be for type SimpleMsg
    constructor(handler: Handler<SimpleMsg>)
    // implementation signature which combines all overloads
    constructor(handler: Handler<T>, transformer?: MsgTransformer<T>) {
        this.handler = handler;
        this.transformer = transformer || defaultTransformer as MsgTransformer<T>;
    }

}

测试用例:

// CAN pass just a handler for a SimpleMsg
const a = new Consumer((msg: SimpleMsg) => true);  // <SimpleMsg>
const b = new Consumer(() => true); // <SimpleMsg>
// CANNOT pass just a handler for another type
const c = new Consumer((msg: { something: string }) => true); // error as expected
// CAN pass a handler and a transformer that match
const d = new Consumer((msg: { something: string }) => true, (msg: SimpleMsg) => ({ something: "" })); // generic <{something: string}>
// CANNOT have mismatch between handler and transformer
const e = new Consumer((msg: { something: string }) => true, (msg: SimpleMsg) => ({})); // error as expected

打字稿游乐场链接

编辑:键入选项对象

如果handlerandtransformer是同一个对象的两个属性,我们不会使用重载。我们将为参数创建一个更复杂的类型。

constructor({handler, transformer}: Options<T>) {

我们可以使用条件类型,transformer仅当SimpleMsg返回的defaultTransformer可分配给时才成为可选类型T

type Options<T> = SimpleMsg extends T ? {
    handler: Handler<T>;
    transformer?: MsgTransformer<T>; // make optional
} : {
    handler: Handler<T>;
    transformer: MsgTransformer<T>;
}

或者我们可以使用联合类型。这不太安全,因为您可以在传递( )T时手动声明为任意类型。但这似乎不太可能成为问题。Handler<SimpleMsg>new Consumer<SomeWrongType>(options)

type Options<T> = {
    handler: Handler<T>;
    transformer: MsgTransformer<T>;
} | {
    handler: Handler<SimpleMsg>;
    transformer?: never; // need this in order to destructure
}

推荐阅读