提问人:Kenya-West 提问时间:1/13/2023 最后编辑:T.J. CrowderKenya-West 更新时间:1/13/2023 访问量:80
在抽象类中需要两种方法之一,或同时需要这两种方法
Require either one of two methods, or both in abstract class
问:
例如,我有这样的代码:
export abstract class AbstractButton {
// Always have to provide this
abstract someRequiredMethod(): void;
// One need to provide one of these (or both)
abstract setInnerText?(): void;
abstract setInnerHTML?(): void;
}
我需要一个继任者来实现 or ,或两者兼而有之。setInnerText()
setInnerHTML()
如何使用人类构建的最强大的类型系统来实现这一点?
答:
我找到了一种获得编译时错误的方法,但它已经够丑陋了,您可能只想要求 mixin 或类似的东西,或者只是只进行运行时测试(从构造函数抛出),这毕竟会在子类开发的早期出现。(我在下面展示了一个运行时检查的示例,因为即使出于下面所述的原因,即使使用编译时检查,您也无论如何都需要它。AbstractButton
但我会详细说明我是如何设法让它给我一个编译时错误。
首先,你不能以这种方式定义那些可选的抽象方法,即使你有它们,它们也是必需的(操场示例)。因此,我们需要定义没有它们的基类,然后找到一种方法来确保子类至少有一个基类。所以现在我们的基类只是:?
abstract class AbstractButton {
// Always have to provide this
abstract someRequiredMethod(): void;
}
我不认为你可以纯粹做你想做的事情,但是你可以用一个 do-nothing 函数确保编译时错误,如果子类没有实现至少一个这些方法,这将导致类型错误。class ___ extends AbstractButton
首先,我们需要一个帮助程序映射类型来检查构造函数的原型是否具有以下任一方法:
type CheckRequired<Ctor> =
[Ctor] extends [ { prototype: { setInnerText: () => void; } } ]
? Ctor
: [Ctor] extends [ { prototype: { setInnerHTML: () => void; } } ]
? Ctor
: {__error__: "AbtractButton subclasses must implement `setInnerText` and/or `setInnerHTML`"}};
我们可以使这些检查更加严格(例如,防止一个类实现一个有效但无效的 [invalid 因为它采用的参数与你想要的参数不同),但这是它的一般形状。setInnerText
setInnerHTML
我们正在测试元组 (, not ) 这一事实很重要,稍后会详细介绍。[Ctor] extends ___
Ctor extends ___
好的,现在我们需要一个函数,它什么都不做,但对它得到的东西进行类型检查:
function checkAbstractButtonSubclass<T>(cls: CheckRequired<T>) {}
现在,不幸的是,我们必须要求人们编写一个调用代码才能得到类型错误:
checkAbstractButtonSubclass(SomeButtonSubclass);
下面是一系列带有调用的类 — 请注意,调用会导致类型错误,因为没有实现 or :Neither
Neither
setInnerText
setInnerHTML
class Both extends AbstractButton {
someRequiredMethod() { }
setInnerText() { }
setInnerHTML() { }
}
checkAbstractButtonSubclass(Both); // <== No error, it provides both
class JustText extends AbstractButton {
someRequiredMethod() { }
setInnerText() { }
}
checkAbstractButtonSubclass(JustText); // <== No error, it provides one of them
class JustHTML extends AbstractButton {
someRequiredMethod() { }
setInnerHTML() { }
}
checkAbstractButtonSubclass(JustHTML); // <== No error, it provides one of them
class Neither extends AbstractButton {
someRequiredMethod() { }
}
checkAbstractButtonSubclass(Neither); // <== Error, it doesn't provide either:
// Argument of type 'typeof Neither' is not assignable to parameter of type '{ __error__: "AbtractButton subclasses must implement `setInnerText` and/or `setInnerHTML`"; }'
必须编写一个毫无意义的函数调用来获得编译时类型错误显然不太理想,但我认为你不能没有它。我确实试图想出一种方法来使函数调用以某种方式有用,但除非您需要子类的注册步骤,否则我还没有想出一种方法。也许比我聪明的人会。:-)
由于人们会忘记编写该调用的代码,因此您需要构造函数执行运行时检查作为编译时检查的后盾(因此,如果编码人员不将调用写入),则在开发早期会失败,例如:AbstractButton
checkAbstractButtonSubclass
constructor() {
if (
(!("setInnerText" in this) || typeof this.setInnerText !== "function") &&
(!("setInnerHTML" in this) || typeof this.setInnerHTML !== "function")
) {
throw new Error(
"AbstractButton subclasses must implement either setInnerText or setInnerHTML. " +
"You can use checkAbstractButtonSubclass(YourClass) to get a proactive compile-time " +
"error for this instead of this runtime error."
);
}
}
最后:那么,为什么实用程序类型要检查而不是仅仅测试?因为我们需要防止泛型类型参数被分发,因为它会破坏测试。通过将其包装在元组中,我们禁用了分发。[Ctor]
[{ prototype: { ___ } }]
Ctor
{ prototype: ____ }
但同样,这真的很笨拙和复杂,并且在构造函数中没有所有额外内容的简单运行时检查将在子类开发的早期出现,所以我想我可能会对此感到满意。AbstractButton
您可以使用类型别名,该别名使用泛型类型参数来区分应需要哪种实现:
type AbstractButton<T extends 'both' | 'html' | 'text' = 'both'> =
& { someRequiredMethod(): void }
& (
T extends 'html' ? { setInnerHTML(): void }
: T extends 'text' ? { setInnerText(): void }
: {
setInnerText(): void;
setInnerHTML(): void;
}
);
然后,您可以使用 implements
子句来确保一致性。以下是上面 TypeScript playground 链接中的一些演示性示例 — 查看更详尽的集合:
class B5 implements AbstractButton<'html'> { /* Error
~~
Class 'B5' incorrectly implements interface 'AbstractButton<"html">'.
Type 'B5' is not assignable to type '{ setInnerHTML(): void; }'.
Property 'setInnerHTML' is missing in type 'B5' but required in type '{ setInnerHTML(): void; }'.(2420) */
someRequiredMethod(): void {}
}
class B6 implements AbstractButton<'html'> { // ok
someRequiredMethod(): void {}
setInnerHTML(): void {}
}
class B7 implements AbstractButton<'text'> { /* Error
~~
Class 'B7' incorrectly implements interface 'AbstractButton<"text">'.
Type 'B7' is not assignable to type '{ setInnerText(): void; }'.
Property 'setInnerText' is missing in type 'B7' but required in type '{ setInnerText(): void; }'.(2420) */
someRequiredMethod(): void {}
}
class B10 implements AbstractButton { /* Error
~~
Class 'B10' incorrectly implements interface 'AbstractButton<"both">'.
Type 'B10' is not assignable to type '{ setInnerText(): void; setInnerHTML(): void; }'.
Property 'setInnerText' is missing in type 'B10' but required in type '{ setInnerText(): void; setInnerHTML(): void; }'.(2420)
input.tsx(7, 7): 'setInnerText' is declared here. */
someRequiredMethod(): void {}
setInnerHTML(): void {}
}
class B12 implements AbstractButton { // ok
someRequiredMethod(): void {}
setInnerHTML(): void {}
setInnerText(): void {}
}
评论
AbstractButton
class
评论
abstract
abstract
abstract