【问题标题】:Type 'null' cannot be used as an index type in Array.reduce() accumulator类型“null”不能用作 Array.reduce() 累加器中的索引类型
【发布时间】:2021-08-19 15:02:55
【问题描述】:

这是一个后续问题: Javascript: How to convert a list of objects with many key-value pairs into one big nested object?

最初的目标是将具有许多键值对的对象列表转换为一个大的嵌套对象。

例如,从这个:

const items = [
        {
            "id": 3,
            "orgId": 2,
            "mod": "toyota",
            "part": "wheel",
            "price": 333
        },
        {
            "id": 4,
            "orgId": 2,
            "mod": "toyota",
            "part": "shell",
            "price": 350
        },
        {
            "id": 9,
            "orgId": 2,
            "mod": "honda",
            "part": "wheel",
            "price": 222
        },
        {
            "id": 10,
            "orgId": 2,
            "mod": "honda",
            "part": "shell",
            "price": 250
        }
    ]


并转换为:


items = {
    "toyota": {"wheel": 333, "shell": 350 }, 
    "honda": {"wheel": 222, "shell": 250 }
}

以下代码适用于 Javascript:

const transformedItems = items.reduce((acc, item) => {
  acc[item.mod] = { ...acc[item.mod], [item.part]: item.price }
  return acc
}, {})

console.log(transformedItems)

怎么样,我想把这个逻辑放到服务器端(用Typescript写的),代码编译不出来:

/Users/john/tmp/dolphin/api/node_modules/ts-node/src/index.ts:293
    return new TSError(diagnosticText, diagnosticCodes)
           ^
TSError: ⨯ Unable to compile TypeScript:
src/utils/billingFunctions.ts:52:11 - error TS2538: Type 'null' cannot be used as an index type.

52       acc[item.mod] = { ...acc[item.mod], [item.part]: item.price }
             ~~~~~~~~~~~~~
src/utils/billingFunctions.ts:52:37 - error TS2538: Type 'null' cannot be used as an index type.

52       acc[item.mod] = { ...acc[item.mod], [item.part]: item.price }
                                       ~~~~~~~~~~~~~
src/utils/billingFunctions.ts:52:53 - error TS2464: A computed property name must be of type 'string', 'number', 'symbol', or 'any'.

52       acc[item.mod] = { ...acc[item.mod], [item.part]: item.price }
                                                       ~~~~~~~~~~~~~~~

    at createTSError (/Users/john/tmp/dolphin/api/node_modules/ts-node/src/index.ts:293:12)
    at reportTSError (/Users/john/tmp/dolphin/api/node_modules/ts-node/src/index.ts:297:19)
    at getOutput (/Users/john/tmp/dolphin/api/node_modules/ts-node/src/index.ts:399:34)
    at Object.compile (/Users/john/tmp/dolphin/api/node_modules/ts-node/src/index.ts:457:32)
    at Module.m._compile (/Users/john/tmp/dolphin/api/node_modules/ts-node/src/index.ts:530:43)
    at Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Object.require.extensions.<computed> [as .ts] (/Users/john/tmp/dolphin/api/node_modules/ts-node/src/index.ts:533:12)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Module.require (internal/modules/cjs/loader.js:952:19)
[nodemon] app crashed - waiting for file changes before starting...


但是,当我尝试时:

const transformedItems = items.reduce((acc, item) => {

  console.log(acc, item.mod)  // print out

  acc[item.mod] = { ...acc[item.mod], [item.part]: item.price }
  return acc
}, {})

控制台日志打印正常:item.mod 是一个字符串。 怎么会抱怨是Type 'null'

【问题讨论】:

  • 你能不能尝试给array#reduce一个类型{} as {[key: string]: any}
  • 你使用的是什么版本的 TypeScript?我看到了一个不同的错误。 Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. No index signature with a parameter of type 'string' was found on type '{}'. 你所有的items 看起来都是这样,还是缺少某些属性?

标签: typescript


【解决方案1】:

使用 reduce 时,您通常必须手动提供累积值的类型,尤其是当您传入空对象 {} 之类的东西时。您可以在参数处添加类型断言,也可以使用函数的泛型参数:

const transformedItems = items.reduce<Record<string, Record<string, number>>>(
    (acc, item) => {
        acc[item.mod] = { ...acc[item.mod], [item.part]: item.price }
        return acc
    }, {})

如果 items 变量是动态传入的并且缺少类型信息,您可能还需要定义其类型,例如使用接口:

interface Item
{
    id: number;
    orgId: number;
    mod: string;
    part: string;
    price: number;
}

然后可以在内联或之前的某个地方使用:

const transformedItems = (items as Item[]).reduce(...

【讨论】:

  • 投射items as Item[] 不安全:const items:any = "hey" 也被默默投射到Item[]。如果问题是items 的类型是any,则此解决方案不会解决错误,而只是为类型检查器伪装。
  • @soimon:嗯,是的,这取决于变量来自何处,应该如何处理。理想情况下,在创建它的上游检查并正确键入该值。
【解决方案2】:

我认为您可以尝试的一种解决方案是强制使用 item.mod,以便 TypeScript 也可以确定它是一个字符串。您只需要像这样将其包装在String(...) 中:

const transformedItems = items.reduce((acc, item) => {

  console.log(acc, item.mod)  // print out

  acc[item.mod] = { ...acc[String(item.mod)], [item.part]: item.price }
  return acc
}, {})

不是我认为的最 TypeScripty 的方式。

【讨论】:

    【解决方案3】:

    您应该确保itemsItem 的数组,而不是any 的数组。像在您的示例中那样编写您的项目数组时,TypeScript 可以自动推断您的所有项目都有一个 mod 字段,它是一个字符串,因为所有示例都有一个。我怀疑您将项目作为参数传递给函数(或动态填充),在这种情况下,您应该自己定义项目的类型。

    这应该让 TypeScript 毫无疑问,items 数组中的每个条目都有一个 mod 和一个 part 字段,这绝对是一个字符串。

    type Item = {
        id: number;
        orgId: number;
        mod: string;
        part: string;
        price: number;
    };
    
    function getTransformedItems(items: Item[]) {
        return items.reduce((acc, item) => {
            acc[item.mod] = { ...acc[item.mod], [item.part]: item.price };
            return acc;
        }, {} as Record<string, Record<string, number>>);
    }
    

    【讨论】:

    • @ConnorLow 谢谢,这是一个危险信号,表明我的工具设置不正确;)修复了答案和我的 IDE。
    【解决方案4】:

    另一种方式,我会说更经典的方式

    让我们添加类型,它会使转换更容易:

    interface CommonData {
        id: number;
        orgId: number;
        mod: string;
        part: string;
        price: number;
    }
    
    interface PartDetails {
        name: string;
        price: number;
    }
    
    interface TransformedData {
        carModel: string;
        details: Array<PartDetails>
    }
    

    变换逻辑:

    function transformData(data: Array<CommonData>): Array<TransformedData> {
        const transformedData: Array<TransformedData> = [];
    
        data.forEach((item: CommonData) => {
            const index = transformedData.findIndex(transformedItem => transformedItem.carModel === item.mod);
            
            if (index === -1) {
                transformedData.push({
                    carModel: item.mod,
                    details: [
                        {
                            name: item.part,
                            price: item.price
                        }
                    ]
                })
            } else {
                const existingTransform = transformedData[index];
    
                existingTransform.details.push({
                    name: item.part,
                    price: item.price
                });
            }
        });
    
        return transformedData;
    }
    

    示例

    输入

    [
        {
            "id": 3,
            "orgId": 2,
            "mod": "toyota",
            "part": "wheel",
            "price": 333
        },
        {
            "id": 4,
            "orgId": 2,
            "mod": "toyota",
            "part": "shell",
            "price": 350
        },
        {
            "id": 9,
            "orgId": 2,
            "mod": "honda",
            "part": "wheel",
            "price": 222
        },
        {
            "id": 10,
            "orgId": 2,
            "mod": "honda",
            "part": "shell",
            "price": 250
        }
    ]
    

    输出

    [
        {
            "carModel": "toyota",
            "details": [
                {
                    "name": "wheel",
                    "price": 333
                },
                {
                    "name": "shell",
                    "price": 350
                }
            ]
        },
        {
            "carModel": "honda",
            "details": [
                {
                    "name": "wheel",
                    "price": 222
                },
                {
                    "name": "shell",
                    "price": 250
                }
            ]
        }
    ] 
    

    【讨论】:

      【解决方案5】:

      根据错误消息,item.mod 字段中有一条记录没有任何值。

      类型 'null' 不能用作 acc[item.mod] 的索引类型 =

      解决方案取决于您打算如何处理这样的记录。

      如果您想保留它,请按照其他答案的建议将 item.mod 转换为 String。

      如果您想省略它,则需要添加如下检查

      const transformedItems = items.reduce((acc, item) => {
        if (item.mod) {
          acc[item.mod] = { ...acc[item.mod], [item.part]: item.price }
        }
        return acc
      }, {})

      在这两种情况下,您处理的数据似乎不一致,必须在使用前进行验证。

      【讨论】:

        猜你喜欢
        • 2018-02-13
        • 2023-01-31
        • 1970-01-01
        • 2021-12-16
        • 2021-07-04
        • 2017-10-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多