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

Typescript 泛型类构造函数中可选字段的默认值,typescript,generics,typescript-generics,conditional-types,Typescript,Generics,Typescript Generics,Conditional Types,我试图弄清楚(仅使用类型声明)是否有可能在泛型类的上下文中为可选字段指定一个具体的默认值,从而使该默认值的类型仍然绑定到另一个必需字段 type Handler<T> = (msg: T) => boolean; type Transformer<T> = (msg: SimpleMsg) => T; class Consumer<T> { handler: Handler<T>;

我试图弄清楚(仅使用类型声明)是否有可能在泛型类的上下文中为可选字段指定一个具体的默认值,从而使该默认值的类型仍然绑定到另一个必需字段

    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
        }

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

因此,我想-以某种方式在变压器(类型?)的术语中定义处理程序的类型(或反之亦然)-并在变压器未定义(即未提供)的情况下强制该类型为
SimpleMsg

我知道可以使用工厂方法或其他方法来解决这个问题,我将处理程序显式定义为
handler
,但我真的想知道它是否可以通过类型和单个入口点来解决


谢谢

A
Transformer
返回
T
,因此我们不可能在不知道
T
是什么的情况下创建默认值。但你的想法是对的:

如果变压器未定义(即未提供),则强制该类型为SimpleMsg

构造函数重载 我们可以通过使用多个参数类型重载
构造函数
函数来实现这一点。对于相同的
T
,我们允许任何匹配的
handler
transformer
函数对,或者只允许使用
SimpleMsg
handler
。在第二种情况下,Typescript无法推断
T
的类型,并将返回
使用者
,因此我们必须将类的
T
的默认值设置为
SimpleMsg

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

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

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

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

Transformer
返回
T
,因此我们不可能在不知道
T
是什么的情况下创建默认值。但你的想法是对的:

如果变压器未定义(即未提供),则强制该类型为SimpleMsg

构造函数重载 我们可以通过使用多个参数类型重载
构造函数
函数来实现这一点。对于相同的
T
,我们允许任何匹配的
handler
transformer
函数对,或者只允许使用
SimpleMsg
handler
。在第二种情况下,Typescript无法推断
T
的类型,并将返回
使用者
,因此我们必须将类的
T
的默认值设置为
SimpleMsg

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

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

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

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

完整的旁注,但它困扰着我,
PlainPojo
是个坏名字。POJO中的P已经代表“平原”。所以,
PlainPojo
是一个“普通的老JavaScript对象”,我有90%是这样的。我得到了它,因此,如果
处理程序
采用了
SimpleMsg
,您只能忽略
转换器
,但我没有得到它来推断
t
在该场景中为
SimpleMsg
。不管怎样,我们可以将
SimpleMsg
设置为默认值:)Complete side note但它让我感到困扰
PlainPojo
是个坏名字。POJO中的P已经代表“平原”。所以,
PlainPojo
是一个“普通的老JavaScript对象”,我有90%是这样的。我得到了它,因此,如果
处理程序
采用了
SimpleMsg
,您只能忽略
转换器
,但我没有得到它来推断
t
在该场景中为
SimpleMsg
。无论如何,我们可以将
SimpleMsg
设置为默认值:)我们不希望它是可选的,除非t是默认值。您可以将两种类型的联合
{handler:handler;transformer:transformer;}{handler:;}
与条件类型一起使用:为了从联合中解构属性,它必须存在于联合中的每个类型上。所以我们说它“存在”,但它是可选的,只能是未定义的。它需要仅基于实现签名工作,而实现签名通常比较模糊。在options对象的情况下,您可以编写它,这样您就不需要断言,但这可能很棘手,因为缩小变量会缩小变量类型,但不会缩小
t
类型。在重载的情况下,您将始终需要断言。我们不希望它是可选的,除非t是默认值。您可以将两种类型的联合
{handler:handler;transformer:transformer;}{handler:;}
与条件类型一起使用:为了从联合中解构属性,它必须存在于联合中的每个类型上。所以我们说它“存在”,但它是可选的,只能是未定义的。它需要仅基于实现签名工作,而实现签名通常比较模糊。就opt而言
// 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
constructor({handler, transformer}: Options<T>) {
type Options<T> = SimpleMsg extends T ? {
    handler: Handler<T>;
    transformer?: MsgTransformer<T>; // make optional
} : {
    handler: Handler<T>;
    transformer: MsgTransformer<T>;
}
type Options<T> = {
    handler: Handler<T>;
    transformer: MsgTransformer<T>;
} | {
    handler: Handler<SimpleMsg>;
    transformer?: never; // need this in order to destructure
}