泛型构造函数中可选字段的默认值 class

Default value for optional field in a constructor of a generic class

我试图弄清楚是否有可能(单独使用类型声明)以某种方式适合可选字段的具体默认值 --- 在泛型 class 的上下文中,这样类型此默认值的一部分仍将绑定到另一个必填字段

    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>,但我真的想知道它是否可以通过类型和单个入口点解决

谢谢!

A Transformer returns T 所以我们不可能在不知道 T 是什么的情况下创建默认值。但是你的想法是对的:

and force that type to be SimpleMsg in case the transformer is undefined (i.e. not provided)

构造函数重载

我们可以通过使用多个参数类型重载 constructor 函数来做到这一点。我们允许任何匹配的 handlertransformer 函数对用于相同的 T 或者只是 handler 接受 SimpleMsg。在第二种情况下,TypeScript 无法推断出 T 的类型,并且会 return Consumer<unknown>,因此我们必须将 class 的 T 的默认值设置为SimpleMsg.

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

(我将 Transformer 重命名为 MsgTransformer 以避免出现重复类型错误)

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

Typescript Playground Link

编辑:键入选项对象

handlertransformer 是同一对象的两个属性的情况下,我们不会使用重载。我们只会为参数创建一个更复杂的类型。

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

我们可以使用条件类型,只有当 SimpleMsg return 为 defaultTransformer 编辑可分配给 T 时,才使 transformer 可选。

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

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

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