在 TypeScript 中为什么我可以这样做:
const element: HTMLInputElement = document.querySelector('input');
但我不能这样做:
const element: HTMLInputElement = event.target;
据我了解,
HTMLInputElement
是从Element
返回的document.querySelector
的子类型。
但在第二种情况下,
HTMLInputeElement
也是EventTarget
的子类型,那么为什么第一行有效而第二行给我一个类型错误?
因为
querySelector
是一个重载的泛型函数,但 Event["target"]
只有一个简单类型。
在使用文字字符串 "input"
作为参数的
特定情况下,您可以这样做,因为
querySelector
具有适用的签名:
querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;
这让它可以将字符串文字参数
"input"
映射到 HTMLInputElement
。 (这也意味着您不需要类型注释,即使您没有,由于该重载, element
的类型也会被推断为 HTMLInputElement | null
。)(在您的示例中,您必须禁用严格的空检查,否则您会收到有关类型上没有
| null
的错误。对于我的所有示例,我都启用了所有严格检查。)相比之下,
Event["target"]
很简单:
readonly target: EventTarget | null;
那里没有什么特别的,所以如果你想将它视为子类型的实例,你必须使用类型断言或谓词。(对我来说)更有趣的案例是:
function example(selector: string) {
const element1 = document.querySelector(selector);
// ^? const element1: Element | null
const element2: HTMLInputElement | null = document.querySelector(selector);
// ^? const element2: HTMLInputElement | null
}
在这种情况下,我上面引用的重载不适用,因为参数只是一个字符串,而不是字符串文字,并且其中可以有任何选择器。这是适用的重载:
querySelector<E extends Element = Element>(selectors: string): E | null;
对于 element1
,由于我们没有提供类型参数,并且 TypeScript 没有任何东西可用于推断它,因此它默认为
Element
,因此调用返回
Element | null
。对于
element2
,为什么它允许呢?即使我们没有使用类型断言或谓词(或显式类型参数),为什么我们可以将
Element | null
分配给
HTMLInputElement | null
?因为 TypeScript 从
element2
的类型推断类型参数。让我们看看它是如何做到的‐
element1
:
element2
:注意推断类型参数的差异。