【问题标题】:How to assert a type of an HTMLElement in TypeScript?如何在 TypeScript 中断言 HTMLElement 的类型?
【发布时间】:2012-09-23 02:35:36
【问题描述】:

我正在尝试这样做:

var script:HTMLScriptElement = document.getElementsByName("script")[0];
alert(script.type);

但它给了我一个错误:

Cannot convert 'Node' to 'HTMLScriptElement': Type 'Node' is missing property 'defer' from type 'HTMLScriptElement'
(elementName: string) => NodeList

除非我将它转换为正确的类型,否则我无法访问脚本元素的“类型”成员,但我不知道如何执行此操作。我搜索了文档和示例,但找不到任何东西。

【问题讨论】:

  • 请注意,这个转换问题在 0.9 中不再存在 - 请参阅下面@Steve 的回答。
  • @GregGum 我没有看到史蒂夫的回答

标签: typescript type-assertion typescript-types typescript-lib-dom


【解决方案1】:

作为CertainPerformanceanswer 的扩展,如果您使用declaration merging 来扩充标准定义库的Document 接口,您可以为getElementsByName 方法(或任何其他就此而言)将参数默认设置为 HTMLElement 以在未显式提供类型参数时模仿非泛型版本的行为:

interface Document
  extends Node,
    DocumentAndElementEventHandlers,
    DocumentOrShadowRoot,
    GlobalEventHandlers,
    NonElementParentNode,
    ParentNode,
    XPathEvaluatorBase {
  getElementsByName<T extends HTMLElement>(elementName: string) : NodeListOf<T>;
}

然后在用户代码中你可以显式传递所需的类型:

const scripts = document.getElementsByName<HTMLScriptElement>("name"); //NodeListOf<HTMLScriptElement>

Playground


请注意,您需要重新指定extends 列表,因为只能合并相同的声明。

【讨论】:

    【解决方案2】:

    与其使用类型断言、类型保护或any 来解决此问题,更优雅的解决方案是使用泛型 来指示您选择的元素的类型。

    不幸的是,getElementsByName 不是通用的,但 querySelectorquerySelectorAll 是通用的。 (querySelectorquerySelectorAll 也更加灵活,因此在大多数情况下可能更可取。)

    如果您将标签名称单独传递给querySelectorquerySelectorAll,由于lib.dom.d.ts 中的以下行,它将自动正确输入:

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

    例如,要选择页面上的第一个脚本标签,就像您的问题一样,您可以这样做:

    const script = document.querySelector('script')!;
    

    就是这样 - TypeScript 现在可以推断出 script 现在是 HTMLScriptElement

    当您需要选择单个元素时使用querySelector。如果需要选择多个元素,请使用querySelectorAll。例如:

    document.querySelectorAll('script')
    

    产生NodeListOf&lt;HTMLScriptElement&gt;的类型。

    如果您需要更复杂的选择器,您可以传递一个类型参数来指示您要选择的元素的类型。例如:

    const ageInput = document.querySelector<HTMLInputElement>('form input[name="age"]')!;
    

    导致ageInput 被键入为HTMLInputElement

    【讨论】:

      【解决方案3】:

      我们可以使用显式返回类型键入我们的变量:

      const script: HTMLScriptElement = document.getElementsByName(id).item(0);
      

      或断言 asTSX 需要):

      const script = document.getElementsByName(id).item(0) as HTMLScriptElement;
      

      或者在更简单的情况下使用 角括号 语法断言。


      类型断言类似于其他语言中的类型转换,但不执行数据的特殊检查或重组。它没有运行时影响,并且纯粹由编译器使用。

      文档:

      TypeScript - Basic Types - Type assertions

      【讨论】:

      • 谢谢!第二个选项对我有用。我的 lint 打印的第一个选项:键入 'HTMLElement | null' 不能分配给类型 'HTMLScriptElement'。类型“null”不可分配给类型“HTMLScriptElement”。 (是我的第一个项目 em Typescript :S 哈哈哈)
      【解决方案4】:
      var script = (<HTMLScriptElement[]><any>document.getElementsByName(id))[0];    
      

      【讨论】:

        【解决方案5】:

        不要键入强制转换。绝不。使用类型保护:

        const e = document.getElementsByName("script")[0];
        if (!(e instanceof HTMLScriptElement)) 
          throw new Error(`Expected e to be an HTMLScriptElement, was ${e && e.constructor && e.constructor.name || e}`);
        // locally TypeScript now types e as an HTMLScriptElement, same as if you casted it.
        

        让编译器为您完成工作,并在您的假设错误时得到错误。

        在这种情况下可能看起来有点过头了,但如果您稍后再回来更改选择器(例如添加 dom 中缺少的类),它将对您有很大帮助。

        【讨论】:

        • 这里看起来很安全,对吧?我们可以保证 e 始终是 HTMLScriptElement 的一个实例,不是吗(除非它不存在,我想)?
        • 我似乎无法让任何类型转换工作,但这工作。
        【解决方案6】:

        澄清一下,这是正确的。

        无法将“NodeList”转换为“HTMLScriptElement[]”

        因为NodeList 不是一个实际的数组(例如,它不包含.forEach.slice.push 等...)。

        因此,如果它确实在类型系统中转换为 HTMLScriptElement[],如果您尝试在编译时对其调用 Array.prototype 成员,则不会出现类型错误,但在运行时会失败。

        【讨论】:

        • 认为这是正确的,但并不完全有用。另一种方法是通过“任何”,它不提供任何有用的类型检查......
        【解决方案7】:

        最后:

        • 一个实际的Array 对象(不是装扮成ArrayNodeList
        • 保证只包含HTMLElements,而不是Nodes 强制转换为HTMLElements 的列表
        • 做正确的事的温暖模糊感觉

        试试这个:

        let nodeList : NodeList = document.getElementsByTagName('script');
        let elementList : Array<HTMLElement> = [];
        
        if (nodeList) {
            for (let i = 0; i < nodeList.length; i++) {
                let node : Node = nodeList[i];
        
                // Make sure it's really an Element
                if (node.nodeType == Node.ELEMENT_NODE) {
                    elementList.push(node as HTMLElement);
                }
            }
        }
        

        享受吧。

        【讨论】:

          【解决方案8】:

          我也会推荐sitepen指南

          https://www.sitepen.com/blog/2013/12/31/definitive-guide-to-typescript/(见下文)和 https://www.sitepen.com/blog/2014/08/22/advanced-typescript-concepts-classes-types/

          TypeScript 还允许您指定不同的返回类型 确切的字符串作为函数的参数提供。例如, DOM 的 createElement 方法的 TypeScript 环境声明 看起来像这样:

          createElement(tagName: 'a'): HTMLAnchorElement;
          createElement(tagName: 'abbr'): HTMLElement;
          createElement(tagName: 'address'): HTMLElement;
          createElement(tagName: 'area'): HTMLAreaElement;
          // ... etc.
          createElement(tagName: string): HTMLElement;
          

          这意味着,在 TypeScript 中,当你调用例如 document.createElement('video'),TypeScript 知道返回值是 一个 HTMLVideoElement 并且能够确保您正在交互 正确使用 DOM Video API,无需键入 assert。

          【讨论】:

            【解决方案9】:

            从 TypeScript 0.9 开始,lib.d.ts 文件使用专门的重载签名,为调用 getElementsByTagName 返回正确的类型。

            这意味着您不再需要使用类型断言来更改类型:

            // No type assertions needed
            var script: HTMLScriptElement = document.getElementsByTagName('script')[0];
            alert(script.type);
            

            【讨论】:

            • 如何在对象表示法中做到这一点?即我不能这样做 {name: : document.querySelector('#app-form [name]').value,}
            • 这行得通:name: ( document.querySelector('#app-form [name]')).value,
            【解决方案10】:

            由于它是NodeList,而不是Array,因此您实际上不应该使用括号或转换为Array。获取第一个节点的属性方式是:

            document.getElementsByName(id).item(0)
            

            你可以这样:

            var script = <HTMLScriptElement> document.getElementsByName(id).item(0)
            

            或者,扩展NodeList

            interface HTMLScriptElementNodeList extends NodeList
            {
                item(index: number): HTMLScriptElement;
            }
            var scripts = <HTMLScriptElementNodeList> document.getElementsByName('script'),
                script = scripts.item(0);
            

            【讨论】:

            • 更新投射现在看起来像这样:const script = document.getElementsByName(id).item(0) as HTMLScriptElement;
            • 即 TS 2.3 的“看起来像这样”。
            【解决方案11】:

            TypeScript 使用 '' 来包围强制转换,所以上面的变成:

            var script = <HTMLScriptElement>document.getElementsByName("script")[0];
            

            但是,很遗憾你不能这样做:

            var script = (<HTMLScriptElement[]>document.getElementsByName(id))[0];
            

            你得到了错误

            Cannot convert 'NodeList' to 'HTMLScriptElement[]'
            

            但你可以这样做:

            (<HTMLScriptElement[]><any>document.getElementsByName(id))[0];
            

            【讨论】:

            • 我认为他们应该进一步研究这一点,假设您使用 $('[type:input]').each( function(index,element) 并且您需要将元素转换为 HTMLInputElement 或 HTMLSelectElement根据您需要设置/获取的属性,强制转换使用 (element).selectedIndex=0; 在元素周围添加 (),有点丑
            • 从长远来看(在 0.9 发布之后),您应该能够将其转换为 NodeList 之类的东西,而且 getElementsByName 将能够使用字符串文字类型覆盖来实现这一点而无需任何铸造!
            • 1.0以后,语法应该是(&lt;NodeListOf&lt;HTMLScriptElement&gt;&gt;document.getElementsByName(id))[0];
            • 你也可以使用 as 来投射。 var script = document.getElementsByName("script")[0] as HTMLScriptElement;
            【解决方案12】:

            如果 TypeScript 将 HTMLCollection 而不是 NodeList 定义为返回类型,则可以在声明文件 (lib.d.ts) 中解决。

            DOM4 也将其指定为正确的返回类型,但旧的 DOM 规范不太清楚。

            另见http://typescript.codeplex.com/workitem/252

            【讨论】:

              【解决方案13】:

              这似乎解决了问题,使用[index: TYPE]数组访问类型,干杯。

              interface ScriptNodeList extends NodeList {
                  [index: number]: HTMLScriptElement;
              }
              
              var script = ( <ScriptNodeList>document.getElementsByName('foo') )[0];
              

              【讨论】:

                【解决方案14】:

                您总是可以使用以下方法破解类型系统:

                var script = (<HTMLScriptElement[]><any>document.getElementsByName(id))[0];
                

                【讨论】:

                • 使用 允许转义类型检查,在开发中并不理想但很酷
                猜你喜欢
                • 2021-12-29
                • 1970-01-01
                • 2019-08-21
                • 2021-09-21
                • 2018-03-08
                • 1970-01-01
                • 1970-01-01
                • 2022-11-21
                相关资源
                最近更新 更多