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
}