我正在创建一个控件管理器,它将成为其他控件管理器的抽象基础:ButtonManager,InputManager,PopupManager等。这些控件具有一些相似之处,但并非全部相似。例如,大小和意图。我想在ControlManager中定义共享类型以及在ControlManager中使用这些类型的接口。
所有控件都有一个意图,但是并非所有控件都具有相同的意图集,因为可以将基本集添加到其中。我希望能够在抽象ControlManager类中创建基本ControlIntent类型,并将其扩展到派生类中。
我应该注意,我将ControlManager作为抽象类,因为我想强制实施实现ControlManager的类来定义某些功能,例如setIntentClass,setSizeClass等
控制管理器将ControlIntent类型定义为
export type ControlIntent = 'Default' | 'Disabled'
扩展ControlManager的ButtonManager然后将其意图类型定义为
export type ButtonIntent = ControlManager.ControlIntent & 'Secondary' | 'Success' | 'Warning' | 'Danger'
ControlManager中的界面定义了一些共享选项。以意图为例:
export interface IOptions {
controlIntent: ControlIntent
}
然后在ButtonManager中,我想扩展options接口并覆盖intent属性:
export interface IOptions extends ControlManager.IOptions {
controlIntent: ButtonIntent
}
可能我错过了全局,但是在我看来,我应该能够至少在基类中定义的类型化选项来强制实现的控制管理器具有一定的大小和意图。意向为“默认”和“禁用”,但无需创建新属性即可在扩展接口中添加新意向。
总结:
所有控件的大小和意图都至少包含一组最少的预定义选项。然后,我可以在不同的控制管理器中使用交集将其添加到预定义的选项中,但是希望能够在基本接口中定义所述选项,然后在派生接口中对其进行扩展。
这是一个实际的设计决策,如果是的话,如何实现?非常感谢所有贡献者。
答案 0 :(得分:1)
通过“添加选项”,您正在做的是扩展类型,而不是扩展它。扩展始终是缩小操作(设置更多限制)。因此,您需要一个并集,而不是一个交集...如果您尝试不重叠地将两个类型相交,则会得到一个等效于div
的空类型(有时编译器实际上会将类型折叠为never
有时它会保持交集,但您会发现无法为其分配任何有用的值):
never
所以您大概是这样的类型:
type ControlIntent = 'Default' | 'Disabled'
// note the parentheses I added because the operators don't have the precedence you think
type ButtonIntent = ControlIntent & ('Secondary' | 'Success' | 'Warning' | 'Danger') // oops
// check with IntelliSense: type Button = never
那很棒,但是缩小/扩展/交集和扩大/超/联合之间的混淆仍然存在于您的界面中。以下定义(我将名称更改为type ControlIntent = 'Default' | 'Disabled'
type ButtonIntent = ControlIntent | ('Secondary' | 'Success' | 'Warning' | 'Danger')
// type ButtonIntent = "Default" | "Disabled" | "Secondary" | "Success" | "Warning" | "Danger"
,以便它可以与IButtonOptions
位于同一命名空间中)现在变成错误:
IOptions
这是因为export interface IOptions {
controlIntent: ControlIntent
}
export interface IButtonOptions extends IOptions { // error!
// ~~~~~~~~~~~~~~
// Interface 'IButtonOptions' incorrectly extends interface 'IOptions'.
controlIntent: ButtonIntent
}
违反了重要的substitution principle:如果IButtonOptions
扩展了IButtonOptions
,则IOptions
对象是一个{ {1}}对象。这意味着如果您要一个IButtonOptions
对象,我可以给您一个IOptions
对象,您会很高兴的。但是,由于您请求的是IOptions
对象,因此您希望其IButtonOptions
属性为IOptions
或controlIntent
。如果您假设的'Default'
对象对'Disabled'
具有其他价值,那么您对我当然不满意。您会看到它,然后说:“等等,这里的IOptions
字符串是什么?
因此,您需要重新设计接口才能使其正常工作。您将不得不放弃controlIntent
是"Secondary"
的子类型的想法。相反,您可以考虑将IButtonOptions
设置为generic类型,其中IOptions
属性的类型可以由通用参数指定。也许像这样:
IOptions
因此,controlIntent
参数必须可分配给export interface IOptions<I extends string = never> {
controlIntent: ControlIntent | I;
}
export interface IButtonOptions extends IOptions<ButtonIntent> {
// don't even need to specify controlIntent here
}
const bo: IButtonOptions = {
controlIntent: "Success";
} // okay
,并且它必须defaults分配给I
,以便没有指定参数的类型string
是相同的输入原始never
。
但是现在,IOptions
不会扩展IOptions
,而是会扩展IButtonOptions
。然后一切正常。
请记住,如果执行此操作,那么现在还必须将用于期望IOptions
对象参数的函数设为通用:
IOptions<ButtonIntent>
好的,希望对您有所帮助。祝你好运!
IOptions