使用TypeScript循环遍历HTMLCollection

25

我想要遍历结果

document.getElementsByTagName("...");

它返回的是一个 HTMLCollection,而不是一个数组。因此我不能简单地使用 forEach。 以下方法是可行的,但看起来不太好:

let elements = document.getElementsByTagName("...");
for (var i = 0, m = elements.length; i < m; i++) {
    let element = elements[i];
}

对于 JavaScript,存在非常类似的问题:

如何循环遍历 HTMLCollection 中的元素?

而且显然,最新版本的现代浏览器都支持:

var list = document.getElementsByClassName("events");
for (let item of list) {
    log(item.id);
}

但 TypeScript 编译器报错:

error TS2495: Type 'NodeListOf<HTMLParagraphElement>' is not an array type or a string type.

尽管如此,它仍然被转译为适当的JavaScript代码。它甚至知道我在这里做什么,并不只是复制源代码。编译后的输出如下:

var elements = main_div.getElementsByTagName("p");
for (var _i = 0, elements_1 = elements; _i < elements_1.length; _i++) {
    var element = elements_1[_i];
}

这很好,因为生成的代码即使在旧版浏览器上也受支持。但我想摆脱错误消息。

我的 compilerOptions 是这样的:

{
    "compilerOptions": {

        "module": "commonjs",
        "target": "es5",
        "sourceMap": true,
        "rootDir": "src",
        "lib": [
            "es2015",
            "dom"
        ]
    }
}

我尝试编辑我的库选项。此特定功能是ES6的一部分,并已经重构了几次。因此,我在lib中测试了各种ecmascript版本,但找不到可用的设置。

5个回答

15

如果你想使用 forEach,可以将它转换为数组。

const elements: Element[] = Array.from(document.getElementsByTagName("..."));
elements.forEach((el: Element) => {
    // do something
})

4
TypeScript编译器在指定额外的标记downlevelIteration后支持此功能:
{
    "compilerOptions": {
        "target": "es5",
        "downlevelIteration": true
    }
}

然而,这不仅会消除错误,还会改变编译器的输出。

以下是输入的TypeScript:

function compileTest(){
  let elements = document.getElementsByTagName("p");
  for(let element of elements){
    console.log(element);
  }
}

被编译成以下JavaScript代码:

function compileTest() {
    var e_1, _a;
    var elements = document.getElementsByTagName("p");
    try {
        for (var elements_1 = __values(elements), elements_1_1 = elements_1.next(); !elements_1_1.done; elements_1_1 = elements_1.next()) {
            var element = elements_1_1.value;
            console.log(element);
        }
    }
    catch (e_1_1) { e_1 = { error: e_1_1 }; }
    finally {
        try {
            if (elements_1_1 && !elements_1_1.done && (_a = elements_1.return)) _a.call(elements_1);
        }
        finally { if (e_1) throw e_1.error; }
    }
}

将目标设置为es5仅仅是为了让“for of”循环与HTMLCollection对象一起工作并不理想。只需使用for循环即可。仅在您想支持旧浏览器(如IE)时才设置为es5。使用ES2015,可以获得更小的捆绑包大小。 - DanKodi
你理解错了,必要的编译器设置是 downleveliteration。它与 es5 没有任何关系(JavaScript 版本是项目的限制)。事实上,如果有什么不同的话,tsc 能够向后兼容一直到 es5 的语法是非常令人印象深刻的。 - lhk

3

示例用法:

const inputs = document.getElementsByTagName("input");
for (let index = 0; index < inputs.length; index++) {
  const input = inputs.item(index);
}

2
你有认真读问题吗?这几乎和第二个代码示例完全一样。 - lhk
几乎了!我的答案使用HTMLCollection(https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection/item)公开的函数item(index)来完成。 - DanKodi
@DanKodi:OP知道这个解决方案可行,但认为它看起来不太好。添加.item可能不会改变这种感觉。 :) - Jan Aagaard

1
为了消除错误信息,您可以将列表添加为任何类型。 TypeScript与JavaScript的区别在于类型。因此,最好的做法是向变量添加类型。供参考,我添加了any类型:
var list: any = document.getElementsByClassName("events");
for (let item of list) {
    console.log(item.id);
}

实际上,这是一种典型的“上下文类型推断”。当表达式的类型由其位置隐含时,就会发生上下文类型推断。对于上面的代码产生类型错误,TypeScript类型检查器使用了document.getElementsByClassName("events")的类型来推断ForLoop函数的类型。如果此函数表达式不在上下文类型化的位置,则list将具有任何类型,并且不会发出错误。
如果上下文类型化的表达式包含显式类型信息,则忽略上下文类型。

上下文类型推断适用于许多情况。常见情况包括函数调用的参数,赋值的右侧,类型断言,对象和数组文字的成员以及返回语句。上下文类型还充当最佳通用类型的候选类型。[来自TypeScript文档]


1

我知道这是对问题的非常晚的回答,但认为在这个主题中可能有用... 在一个Angular项目中,我正在寻找一种使用数组解构(谁不喜欢数组解构呢?)HTMLCollectionOf<T>的方法,如下面的示例所示:

const [zoomControl] = document.getElementsByClassName('leaflet-control-zoom');

但会出现错误:

'Type 'HTMLCollectionOf' must have a 'Symbol.iterator' method that returns an iterator.ts(2488)'

此外,我有时需要遍历集合,例如:

const elementsWithClassName = document.getElementsByClassName('class-name');
for(const a of elementsWithClassName) {
  console.log(a)
}

我发现添加一个 global-types-extensions.d.ts 文件,其内容如下:
declare global {
  interface HTMLCollectionOf<T extends Element> {
    [Symbol.iterator](): Iterator<T>;
  }
}
export {};

我已经解决了 TypeScript 给出的错误/警告。

现在,我可以愉快地使用数组解构和 for...of 来处理任何 HTMLCollectionOf 了。


嗯,你看过被接受的答案了吗?这个问题正被奇怪的解决方法淹没,而实际上存在一个非常干净简洁的解决方案。只要在编译器标志中指定即可。 - lhk
我确实查看了被接受的答案并尝试实施建议更改到.tsconfig文件; 然而,这并没有解决我的问题。也许,这是因为Angular默认现在针对es2015 - Batuik
你添加的声明应该与正确配置的tsconfig所添加的声明几乎相同。你确定你已经做好了吗?例如,你写的是.tsconfig,这实际上是你使用的名称吗?它应该是tsconfig.json - lhk
没错! 我指的是 tsconfig.json 文件,并且在我之前的评论中错误地将该文件称为“.tsconfig”。似乎我现在无法编辑我的评论了。根据您的答案,我开始寻找可能导致建议的解决方法对我不起作用的原因;我发现将 DOM.Iterable 添加到 lib 数组中对我很有帮助。事实证明,当目标为 ES2015 时,甚至不需要在 compilerOptions 中指定 "downlevelIteration": true。感谢有关正确配置 tsconfig 的提示! - Batuik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接