簡體   English   中英

Typescript 將對象列表減少為具有不同形狀的 object

[英]Typescript reduce a list of objects to an object with a different shape

如果沒有一些笨拙的類型斷言,我這輩子都無法弄清楚如何修改 reduce 的返回值。 例如:

const list: Array<Record<string, string | number>> = [
  {
    resourceName: "a",
    usage: 20
  },
  {
    resourceName: "b",
    usage: 50
  }
];

const toMap = list.reduce(
  (acc, item) => ({
    ...acc,
    [item.resourceName]: item.usage
  }),
  {}
);

toMap的類型為Record<string, string | number> Record<string, string | number>作為list的形狀為Array<Record<string, string | number>> Array<Record<string, string | number>>但我不關心我的輸入列表的形狀,我關心我的 output object 的形狀,即Record<string, number>

在這個例子中,我可以在末尾添加一個類型斷言以獲得正確的類型,但有時list類型和 output 類型不匹配,並且必須使用as unknown as X來進行像reduce這樣常見的操作似乎不正確至少可以說。

您確實需要關心輸入列表的形狀,至少在您希望編譯器理解item.usage是一個number而不是string | number的范圍內。 string | number 如果將list 注釋Array<Record<string, string | number>> Array<Record<string, string | number>> ,然后你明確告訴編譯器忘記關於list的所有內容,除了它是一個屬性類型為string | number的對象數組。 string | number 在此之后, reduce()item.usage的任何使用都必然涉及string | number string | number ,除非您使用類型斷言之類的東西來向編譯器返回您之前丟棄的一些信息。

在這種情況下,我建議不要使用list的類型注釋,讓編譯器從初始化值推斷其類型:

const list = [
  {
    resourceName: "a",
    usage: 20
  },
  {
    resourceName: "b",
    usage: 50
  }
];

/* const list: {
    resourceName: string;
    usage: number;
}[] */

您可以看到,這個推斷的類型是一個對象數組,其resourceName屬性為string類型,而usage屬性為number類型。 對於您的目的,這是一個更有用的list分類。


現在你想使用reduce() 這里的問題是您傳入了一個初始累加器,它是一個空的 object {} 留給它自己的設備,編譯器將推斷{}作為這個初始累加器的類型,並由此推斷reduce()調用簽名上的泛型類型參數也是{}類型,最后得出返回值也是{}類型。 (有時編譯器的類型推斷會為您提供所需的類型,而有時則不會。這就是啟發式算法的方式。)如果您希望發生其他事情,則需要防止這種推斷。

一種方法是提前聲明初始累加器,其類型顯式注釋為您想要的:

const init: Record<string, number> = {};
const toMap = list.reduce(
  (acc, item) => ({
    ...acc,
    [item.resourceName]: item.usage
  }),
  init
);
// const toMap: Record<string, number>

現在編譯器不需要推斷init的類型; 並且reduce()中的類型參數被推斷為Record<string, number> ,因此toMap也是Record<string, number>

另一種方法是在調用reduce()時手動指定類型參數。 這可以防止類型參數的推斷,並允許編譯器根據上下文推斷初始累加器的類型:

const toMap = list.reduce<Record<string, number>>(
  (acc, item) => ({
    ...acc,
    [item.resourceName]: item.usage
  }),
  {}
);
// const toMap: Record<string, number>

無論哪種方式都應該適合你。

Playground 代碼鏈接

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM