【发布时间】:2022-11-30 00:04:54
【问题描述】:
我有以下代码:(TS Playground link)
type TDeliveriesStatsDatum<TName = string> = {name: TName; value: number};
type TDeliveriesStatsData<TName = string> = TDeliveriesStatsDatum<TName>[];
interface IDeliveriesStats {
meta: {
delivery_count: number;
transport_count: number;
};
progress: TDeliveriesStatsData<"done" | "not_done" | "cancelled">;
disputes: TDeliveriesStatsData<"disputed" | "undisputed">;
loading_punctuality: TDeliveriesStatsData<"ontime" | "delayed" | "unknown">;
unloading_punctuality: TDeliveriesStatsData<"ontime" | "delayed" | "unknown">;
cmr_document: TDeliveriesStatsData<"done_with_cmr_document" | "done_without_cmr_document">;
non_cmr_document: TDeliveriesStatsData<
"done_with_non_cmr_document" | "done_without_non_cmr_document"
>;
// for delays and durations, name is a string containing the values in minutes of the steps
// ex: "_15", "15_30" and "30_" for "less than 15 minutes", "between 15 and 30 minutes" and "more than 30 minutes"
loading_delay: TDeliveriesStatsData;
unloading_delay: TDeliveriesStatsData;
loading_duration: TDeliveriesStatsData;
unloading_duration: TDeliveriesStatsData;
}
type DeliveriesStatsKeys = "progress" | "disputes" | "cmr_document" | "non_cmr_document";
type TPieChartData<T extends DeliveriesStatsKeys> = {
augmentedData: {name: string, value: number, dataKey: string, fill: string}[]
} & {
[K in IDeliveriesStats[T][0]["name"]]: number;
};
export const formatPieChartData = <K extends DeliveriesStatsKeys>(
data: IDeliveriesStats[K]
): TPieChartData<K> => {
return data.reduce(
(acc: TPieChartData<K>, datum: IDeliveriesStats[K][0]) => {
// keep these values to extract them for KPIs
acc[datum.name] = datum.value;
acc.augmentedData.push({
...datum,
dataKey: datum.name,
fill: "colorsAndLabelsByDataKey[datum.name].fill," // written this way to avoid adding too much unrelated code
});
return acc;
},
{augmentedData: []} as TPieChartData<K>
);
};
编译器错误如下:
This expression is not callable.
Each member of the union type '{ (callbackfn: (previousValue: TDeliveriesStatsDatum<"done" | "not_done" | "cancelled">,
currentValue: TDeliveriesStatsDatum<"done" | "not_done" | "cancelled">,
currentIndex: number, array: TDeliveriesStatsDatum<...>[]) => TDeliveriesStatsDatum<...>): TDeliveriesStatsDatum<...>;
(callbackfn: (previousValue: TDeliver...'
has signatures, but none of those signatures are compatible with each other.
我如何输入 IDeliveriesStats 或 TPieChartData 让 Typescript 理解 acc[datum.name] 是正确输入的?
【问题讨论】:
-
这是 TS 不直接支持的另一个影响相关联盟如 ms/TS#30581 中所述,推荐的修复是 ms/TS#47109 中描述的某种重构。不仅如此,这里的示例的最终代码看起来像 this playground link。如果这完全解决了您的问题,我可以写一个完整的答案。否则,我错过了什么?
-
@jcalz 谢谢,这似乎是一个很好的解决方案。我不确定我是否完全理解你是如何想到它的;)另外我认为重新定义 _acc 来转换似乎有点奇怪?我会这样写:
(acc as Record<DeliveriesStatsName[K], number>)[datum.name] = datum.value; -
通过
x as T的类型断言允许扩大(通常是安全的)和缩小(通常是不安全的)。变量上的类型注释(通过const _x: T = x)只允许加宽。因此,如果您正在做一些安全的事情并希望编译器对其进行验证,则注释比断言更可取。如果您只是在寻找易用性,那么类型断言就可以了,但是您并没有真正让 TypeScript 按照要求“理解acc[datum.name]是正确键入的”。当我写下答案时,我会解释更多。 -
谢谢,我不明白为什么我们首先需要扩大类型;为什么 TS 不理解直接将
datum.name分配给acc? (它失败了Type 'number' is not assignable to type 'PieChartData<K>[DeliveriesStatsName[K]]'.) -
我不知道它为什么失败的权威来源;通常,编译器在使用(通用)类型
K的键索引到Record<K, V> & Foo时会感到困惑。加宽本质上是解决这个问题的一种方法。
标签: typescript