Bug报告
🔎 搜索关键词
- liskov
- addEventListener
- overload
🕗 版本与回归信息
- 在我尝试的每个版本中,这种行为都是正常的,我已经查阅了关于所有问题的常见问题解答。我已经检查了完整的常见问题解答。
⏯ Playground链接
带有相关代码的Playground链接
💻 代码
interface FooBarEventMap {
click: CustomEvent;
}
class FooBarElement extends HTMLElement {
}
interface FooBarElement extends HTMLElement {
addEventListener<T extends keyof FooBarEventMap>(
type: T,
listener: (this: FooBarElement, ev: FooBarEventMap[T]) => any,
options?: boolean | AddEventListenerOptions,
): void;
}
interface HTMLElementTagNameMap {
"foo-bar": FooBarElement;
}
let one: NodeListOf<Node> = document.querySelectorAll('foo-bar')!;
one.forEach(el => el.addEventListener('click', null));
let two: NodeListOf<Node> = document.querySelectorAll('div')!;
two.forEach(el => el.addEventListener('click', null));
let three: NodeListOf<FooBarElement> = document.querySelectorAll('foo-bar')!;
three.forEach(el => el.addEventListener('click', null));
let four: NodeListOf<HTMLDivElement> = document.querySelectorAll('div')!;
four.forEach(el => el.addEventListener('click', null));
🙁 实际行为
对于 one
的赋值报告了一个错误。
从技术上讲,这是正确的: FooBarElement
是 HTMLElement
的子类型,而 HTMLElement
又是一个 Node
的子类型。FooBarElement.addEventListener()
不接受 Node.addEventListener()
所接受的所有参数,因此这是一个 LSP 违规。
然而,对于 HTMLDivElement
也是如此。对 two
的赋值被接受,但正如对 four
的赋值所证明的那样,一个 HTMLDivElement
实际上并不接受 null
。
当我将 addEventListener
的重载复制到:
interface FooBarElement extends HTMLElement {
addEventListener<T extends keyof FooBarEventMap>(
type: T,
listener: (this: FooBarElement, ev: FooBarEventMap[T]) => any,
options?: boolean | AddEventListenerOptions,
): void;
addEventListener<T extends keyof FooBarEventMap>(
type: T,
listener: (this: FooBarElement, ev: FooBarEventMap[T]) => any,
options?: boolean | AddEventListenerOptions,
): void;
}
... 这两个重载完全相同。然后对 one
的赋值不再报告为错误。
🙂 预期行为
我不希望看到这种不一致的行为:
- 当将
NodeListOf<Node>
分配给 HTMLDivElement 和 FooBarElement 时,应该报告错误,或者两者都不应该报告错误。 - 添加一个相同的额外重载不应该使错误消失。
其他上下文
实际世界的应用场景是使用具有 strict: true
配置的 tsconfig.json 来消费 WoltLab/d.ts 仓库。
在该仓库中有一个元素具有不兼容的重载:
https://github.com/WoltLab/d.ts/blob/57f923f03e37885885326bbd622d15b554feb9b5/WoltLabSuite/Core/Element/woltlab-core-dialog.d.ts#L40
并且它在 global.d.ts 中的注册位置是:
https://github.com/WoltLab/d.ts/blob/57f923f03e37885885326bbd622d15b554feb9b5/global.d.ts#L122
现在,当尝试编译一个将该仓库作为依赖项的项目时,会报告以下错误:
node_modules/typescript/lib/lib.dom.d.ts:10535:87 - error TS2344: Type 'HTMLElementTagNameMap[K]' does not satisfy the constraint 'Node'.
Type 'HTMLInputElement | HTMLElement | WoltlabCoreDialogElement | HTMLDialogElement | WoltlabCoreDialogControlElement | ... 66 more ... | HTMLVideoElement' is not assignable to type 'Node'.
Type 'WoltlabCoreDialogElement' is not assignable to type 'Node'.
Types of property 'addEventListener' are incompatible.
Type '<T extends keyof WoltlabCoreDialogEventMap>(type: T, listener: (this: WoltlabCoreDialogElement, ev: WoltlabCoreDialogEventMap[T]) => any, options?: boolean | ... 1 more ... | undefined) => void' is not assignable to type '(type: string, callback: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions | undefined) => void'.
Types of parameters 'listener' and 'callback' are incompatible.
Type 'EventListenerOrEventListenerObject | null' is not assignable to type '(this: WoltlabCoreDialogElement, ev: CustomEvent<any> | CustomEvent<ValidateCallback[]>) => any'.
Type 'null' is not assignable to type '(this: WoltlabCoreDialogElement, ev: CustomEvent<any> | CustomEvent<ValidateCallback[]>) => any'.
10535 querySelectorAll<K extends keyof HTMLElementTagNameMap>(selectors: K): NodeListOf<HTMLElementTagNameMap[K]>;
5条答案
按热度按时间kupeojn61#
相关问题:是否可以自动继承父类型(即HTMLElement)的所有重载,以添加更具体的重载,还是我需要使用复制粘贴?
wz8daaqr2#
这是一个不需要DOM库的复现示例。
工作假设:这里的核心问题在于(为了避免组合爆炸),当源签名被重载时,我们取签名的泛型约束的擦除形式,这实际上将
转换为
这就是为什么
Demo2Map
的内容对复现至关重要,因为如果它的任何条目仅仅是MyEvent
,那么就不再有错误了——我们可能在进行逆变操作时进行了可疑的子类型约简,而实际上应该进行超类型约简。话虽如此,我们四个人花了一个小时研究这个问题,但我们仍然不确定
| MyEventListenerObject
为什么在这里很重要。两种可能的结果是这个根本无法修复,或者经过四十个小时的调试后,有人可能会产生一个三行修复方案。在我们尝试之前很难说。
0vvn1miw3#
@RyanCavanaugh 谢谢你。我在那里找到了什么?
你是否能给我关于后续评论中的问题的一些提示?
是否有可能自动继承父类型(即HTMLElement)的所有重载,以添加一个更具体的重载,还是我需要手动复制粘贴?
我们的
addEventListener
定义实际上对我来说是无效的(即我们拒绝在自定义元素中调用HTMLElement的有效调用)。如果我有一些语法可以继承HTMLElement["addEventListener"]
的所有重载,然后再添加我的单个更具体的重载,这可能会解决这个问题的症状(由于重载),同时使定义更加正确。我想避免将原始定义从HTMLElement
复制到每个自定义元素中。fquxozlt4#
不,很遗憾,我们目前没有一个明确的方法来表示"所有那些重载加上这个",你可以通过交集来实现一些技巧,例如:
请注意,这个操作
&
是顺序敏感的(就像重载本身一样)am46iovg5#
感谢您。参考:以下内容似乎在我们的实际应用场景中起作用。很高兴您提到顺序很重要,新的重载需要先出现,因为它更具体: