非递归遍历TypeScript代码出现Element implicitly has an 'any' type类型错误求助
问题描述
我正尝试将一个非递归遍历对象的JavaScript函数改写为TypeScript,访问entry[key]时触发如下TypeScript错误:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'StringObjectOrArray'.
No index signature with a parameter of type 'string' was found on type 'StringObjectOrArray'.ts(7053)
我对此感到困惑:for..in遍历数组时会返回字符串形式的索引,而字符串形式的整数本可直接作为数组索引使用,我到底忽略了什么?
我的TypeScript代码:
type StringKeyObject = { [key: string]: unknown }; type StringObjectOrArray = StringKeyObject | StringKeyObject[]; function traverse(obj: StringObjectOrArray) { if (!obj) { return; } const stack: [string, StringObjectOrArray][] = [['root', obj]]; while (stack.length) { const [path, entry] = stack[0]; for (const key in entry) { if (entry[key] && typeof entry[key] === "object") { // ts error here stack.push([`${path}.${key}`, entry[key]]); } else { console.log("%s: %s", `${path}.${key}`, entry[key]); } } stack.shift(); } }
可正常运行的JavaScript代码如下:
function traverse(obj) { if (!obj) { return; } const stack = [['root', obj]]; while (stack.length) { const [path, entry] = stack[0]; for (const key in entry) { if (entry[key] && typeof entry[key] === "object") { stack.push([`${path}.${key}`, entry[key]]); } else { console.log("%s: %s", `${path}.${key}`, entry[key]); } } stack.shift(); } } traverse({ one: "hello", two: "world", three: { one: 1, two: 2, three: 4, four: [{ one: true, two: false }] }, four: "!" });
问题原因与解决方案
原因分析
TypeScript的类型检查严格区分了对象和数组的索引类型:
- 你定义的
StringKeyObject使用字符串索引,但数组的索引类型在TS里是number(虽然JS里用字符串数字能访问数组,但TS类型系统不认可字符串作为数组的合法索引类型)。 StringObjectOrArray是对象和数组的联合类型,当你用string类型的key去索引这个联合类型时,TS找不到同时支持字符串索引的类型定义,因此抛出错误。
解决方案
推荐通过类型收窄的方式处理,这更符合TS的类型安全规范,修正后的代码如下:
type StringKeyObject = { [key: string]: unknown }; type StringObjectOrArray = StringKeyObject | StringKeyObject[]; function traverse(obj: StringObjectOrArray) { if (!obj) { return; } const stack: [string, StringObjectOrArray][] = [['root', obj]]; while (stack.length) { const [path, entry] = stack.shift()!; if (Array.isArray(entry)) { // 处理数组:把字符串索引转为number类型访问 for (const key in entry) { const value = entry[Number(key)]; if (value && typeof value === "object") { stack.push([`${path}.${key}`, value as StringObjectOrArray]); } else { console.log("%s: %s", `${path}.${key}`, value); } } } else { // 处理对象:直接用字符串索引访问 for (const key in entry) { const value = entry[key]; if (value && typeof value === "object") { stack.push([`${path}.${key}`, value as StringObjectOrArray]); } else { console.log("%s: %s", `${path}.${key}`, value); } } } } }
如果想简化代码,也可以给数组类型添加字符串索引签名,让TS认可字符串索引:
type StringKeyObject = { [key: string]: unknown }; // 给数组扩展字符串索引签名 type StringObjectOrArray = StringKeyObject | (StringKeyObject[] & { [key: string]: unknown }); function traverse(obj: StringObjectOrArray) { if (!obj) { return; } const stack: [string, StringObjectOrArray][] = [['root', obj]]; while (stack.length) { const [path, entry] = stack[0]; for (const key in entry) { const value = entry[key]; if (value && typeof value === "object") { stack.push([`${path}.${key}`, value as StringObjectOrArray]); } else { console.log("%s: %s", `${path}.${key}`, value); } } stack.shift(); } }
补充说明
JS中字符串形式的整数确实能访问数组,但TS为了保证类型安全,强制要求数组使用number类型索引。通过类型收窄或者扩展索引签名,就能让TS正确识别索引的合法性,消除错误。
内容的提问来源于stack exchange,提问作者bigless
相关产品推荐
相关产品推荐

