Typescript,“NodeListOf<Element>”不是数组类型或字符串类型。

41

将我的JS转换为TS严格模式。

以下语法在我看来没问题,但是在for循环中对allSubMenus出现了TS的错误提示:

[ts] Type 'NodeListOf<Element>' is not an array type or a string type.

我错过了什么?

function subAct(target:Node){

  const allSubMenus : NodeListOf<Element> = document.querySelectorAll('.subMenuItems') 

  for (const sub of allSubMenus){
    sub.classList.remove('active')
  }  
}
7个回答

40

您需要将编译器选项target设置为es6或更高版本,才能使NodeListOf<T>可迭代。


1
你需要手动循环索引,类似这样:for (let i = 0; i < allSubMenus.length; i++) { allSubMenus[i].classList.remove('active'); } - Matt McCutchen

39

根据您的 TypeScript 目标编译器,可能会出现解析错误。

for-of 循环是在 EcmaScript(ES6)的第六版中引入的,因此旧的浏览器 JS 引擎无法理解 for-of 循环语法。 https://hacks.mozilla.org/2015/04/es6-in-depth-iterators-and-the-for-of-loop/

要解决这个问题,

如果您仅支持最新的现代浏览器(>=ES6)

请更改您的 TS 目标。

//tsconfig.json
{
  "compilerOptions": {
    "target": "es6" //above es6 like ES2015,ES2018 ...
  }
}

如果您支持旧版浏览器(<=ES5)

我假设您正在使用下一个环境。

//.tsconfig.json
{
  "compilerOptions": {
    "target": "es5"
  }
}
  1. 要使用as any语法来保持for-of循环

注意:“as any”会将集合(对象)强制转换为数组,并且这将影响“for”范围内的某些类型特性。

//.ts
const allSubMenus : NodeListOf<SpecifiedElement> = document.querySelectorAll('.subMenuItems') 
    
for (const sub of allSubMenus as any){ // then will pass compiler
  sub.classList.remove('active')
}  

上面的TS脚本将被编译为:

//.js output
var allSubMenus = document.querySelectorAll('.subMenuItems');

for (var _a = 0, _b = forms; _a < _b.length; _a++) {
    var sub = _b[_a];
    sub.classList.remove('active');
}

https://stackblitz.com/edit/node-ywn1bq?file=main.js

  1. 使用传统的for循环语法
const allSubMenus : NodeListOf<SpecifiedElement> = document.querySelectorAll('.subMenuItems') 

for (let i = 0; i < allSubMenus.length; i++) { 
  allSubMenus[i].classList.remove('active'); 
}    

<元素>

除了上述内容之外,为避免以下警告,

Property '<property name>' does not exist on type 'Element'

如果您知道元素类型并且类型定义存在,可以指定<Element>。

//for example,
NodeListOf<Element>  => NodeListOf<HTMLFormElement>

ECMAScript(ES) 历史

https://codeburst.io/javascript-wtf-is-es6-es8-es-2017-ecmascript-dca859e4821c


1
我一直在尝试谷歌这个修复方法。你的答案对我非常有效。谢谢。 - Tony Stevanovich
4
撇开答案的优点不谈,对于任何读到这篇文章的人,请不要使用那种方法!将 any 强制转换会关闭类型检查——你既失去了 sub 的所有类型安全性,也失去了支持 IDE 的自动完成功能(除非你重新转换回来)。另外值得注意的是,默认情况下 querySelectorAll 返回的是 NodeListOf<Element> ,因此无需明确指定 allSubMenus 变量的类型。 - Oleg Valter is with Ukraine
整个强制转换的目的是为了避免你随意使用任何类型。最好的方法是将目标编译器更改为正确地对代码进行类型定义,以便以后不会遇到“任何”问题。 - Ryan Dennler
@RyanDennler,请查看“注意事项”部分。如果用户使用相对较新的TS编译器,则用户不会遇到此问题。开发环境可能会有所不同,更改TS目标可能不是每个人的选择。 - John
@MaciejKrawczyk,您能否分享一下是什么让您认为“这会让旧版浏览器崩溃”? - John
显示剩余2条评论

8

您可以尝试

const allSubMenus : NodeListOf<Element> = document.querySelectorAll('.subMenuItems') 
Array.from(allSubMenus, subMenu => {/* */})

2
在你的tsconfig.json文件的compilerOptions中设置"downlevelIteration": true。
来自于https://www.typescriptlang.org/tsconfig#downlevelIteration 引用: 下降级别是TypeScript的术语,用于将代码转换为旧版JavaScript。这个标志是为了支持现代JavaScript在旧版JavaScript运行时中遍历新概念的更准确实现。

1

假设您想保留ES5目标(如果不是,将其升级到ES6也可以解决问题)。

  1. 使用for循环
function subAct(target:Node){
  const allSubMenus = document.querySelectorAll('.subMenuItems');

  for (let i = 0; i < allSubMenus.length; i += 1){
    const sub = allSubMenus[i];
    sub.classList.remove('active')
  }  
}

使用 Array.from 为了使用它,您必须将ES2015.core添加到compilerOptions.lib并添加Array.prototype.from的polyfill。请注意,这将在集合上循环两次 - 第一种方法更好。
function subAct(target:Node){
  const allSubMenus = Array.from(document.querySelectorAll('.subMenuItems'));

  for (const sub of allSubMenus){
    sub.classList.remove('active')
  }  
}

如果您使用“Array.from”,则需要更改编译目标或“lib”编译器选项为“es2015”或更高版本,对吗? - John
@John 正确,将 ES2015.core 添加到库中,并导入数组填充(即从 core-js 中导入)。我会更新我的答案。 - Maciej Krawczyk

1

在编译代码时,您可以选择 target: ES2017target: ES2015。此外,您还可以在 tsconfig.json 文件中的 compilerOptions 下设置此属性。

以下是相应的命令行代码:

npx tsc path/to/file.ts --target ES2015

提示: 如果您同时使用babel和typescript,强烈建议您始终让typescript编译为最新版本的Javascript,然后让babel处理其余的转换过程。通过这种技术,您可以为旧版浏览器添加另一个支持级别的保证,因为typescript不能编译在例如ie6中运行的代码;因此,babel在这里拯救了您,并确保您的js代码可以在甚至ie < 9中运行,具有有用的polyfill和其他机制来实现!

所以请记住:

始终让typescript将您的代码编译为最新的javascript(通过设置target: ES2017),并让babel转换您的js代码以支持旧版浏览器(适当地分离关注点,让每个人都做相关的工作)。


1

在撰写本文时,是可以做到的。然而,forEach没有break,因此它并不总是完全替代... - minusf

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