在Typescript 中,我为什么可以这样做:

const element: HTMLInputElement = document.querySelector('input');

但我不能这么做:

const element: HTMLInputElement = event.target;

据我所知,HTMLInputElement是从document.querySelector返回的Element的亚型.

但是在第二种情况下,HTMLInputeElement也是EventTarget的子类型,那么为什么第一行可以工作,而第二行给我一个类型错误呢?

EDIT:我禁用了严格的空值判断,因此这里不需要处理空值.

推荐答案

在Typescript 中,为什么我可以这样做……但我不能这样做……

因为querySelector是一个重载的泛型函数,而Event["target"]只有一个简单类型.不能在不缩小范围(或断言)的情况下将超类型(如EventTargetElement)赋给子类型(如HTMLInputElement).现在发生的情况是,querySelector的返回类型相当复杂,因此您实际上并没有这样做.

在使用文字字符串"input"作为参数的specific示例中,您可以这样做,因为querySelector具有适用的签名:

querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;

这使它可以将字符串文字参数"input"映射到HTMLInputElement.(这也意味着您不需要类型注释,即elementwould be inferredHTMLInputElement | null的类型,即使您没有它,这要归功于那个重载.)

相比之下,Event["target"]仅仅是:

readonly target: EventTarget | null;

没有什么特别的事情发生,所以如果您想将其作为子类型的实例处理,则必须使用类型断言或谓词.


(对我来说)更有趣的 case 是:

function example(selector: string) {
    const element1 = document.querySelector(selector);
    //    ^? const element1: Element | null

    const element2: HTMLInputElement | null = document.querySelector(selector);
    //    ^? const element2: HTMLInputElement | null
}

Playground link

在这种情况下,我上面引用的重载不适用,因为参数只是一个字符串,而不是字符串文字,并且其中可能有任何 Select 器.这是适用于以下情况的过载:

querySelector<E extends Element = Element>(selectors: string): E | null;

对于element1,因为我们没有提供类型参数,并且类型脚本没有任何东西可以用来推断它,所以它缺省为Element,所以调用返回Element | null.

然而,对于element2英镑,它为什么会允许这样做?为什么我们没有使用类型断言或谓词(或显式类型参数)就可以将Element | null赋给HTMLInputElement | null?相反,TypeScrip正在从element2‘S类型中推断类型参数.

让我们看看它是怎么做的-

element1:

TypeScript playground showing that for element1 the call is querySelector<Element>(selectors: string): Element | null

element2:

TypeScript playground showing that for element2 the call is querySelector<HTMLInputElement>(selectors: string): HTMLInputElement | null

请注意推断类型参数的不同之处,从而与返回类型不同.由于推断的类型参数,结果是HTMLINputElement | null,因此赋值是有效的.


(For my various examples, I have all strict checks enabled, but just read past the 100s.😊)

Typescript相关问答推荐

"@ clerker/nextjs/server没有名为clerkMiddleware的导入成员'

如何修复VueJS中的val类型不能赋给函数

如何获取数组所有可能的索引作为数字联合类型?

Typescript对每个属性具有约束的通用类型

在使用Typescribe时,是否有一种方法可以推断映射类型的键?

未在DIST中正确编译TypeScrip src模块导入

为什么T[数字]不也返回UNDEFINED?

是否可以针对联合类型验证类型属性名称?

错误TS2403:后续变量声明必须具有相同的类型.变量';CRYPTO';的类型必须是';CRYPATO';,但这里的类型是';CRYPATO';

如何在深度嵌套的Reaction路由对象中隐藏父级?

布局组件中的Angular 17命名路由出口

我在Angular 17中的environment.ts文件中得到了一个属性端点错误

如何将对象数组中的键映射到另一种对象类型的键?

创建依赖函数参数的类型

抽象类对派生类强制不同的构造函数签名

设置不允许相同类型的多个参数的线性规则

有没有更干净的方法来更新**key of T**的值?

Typescript:是否将联合类型传递给重载函数?

如何根据Typescript 中带有泛型的对象嵌套键数组获取数组或对象的正确类型?

带动态键的 TS 功能