[英]TypeScript data mapper function argument infers never
考慮以下示例:
type columns = {
A: number;
B: string;
C: boolean;
};
const mapper: { [T in keyof columns]: (item: columns[T]) => string } = {
A: item => `${item}`,
B: item => item,
C: item => (item ? "Good" : "Bad"),
};
const data: columns[] = [
{ A: 0, B: "Hello", C: true },
{ A: 1, B: "World", C: false },
];
const keys: (keyof columns)[] = ["A", "B", "C"];
data.map(item => keys.map(key => mapper[key](item[key] as never)));
// should return [["0", "Hello", "Good"], ["1", "World", "Bad"]]
在最后一行, key
的類型為keyof columns
,即"A" | "B" | "C"
"A" | "B" | "C"
"A" | "B" | "C"
,這使得mapper[key]
減少到(item: number & string & boolean) => string
,即(item: never) => string
。 (指出我的概念是否錯誤。)
所以問題是,我怎樣才能重寫代碼使得item[key]
不需要強制轉換為never
?
這是 TypeScript 和microsoft/TypeScript#30581主題的一般限制。 編譯器確實無法查看像mapper[key](item[key])
這樣的單個表達式並使用控制流分析來分析它以查看它是否安全。
問題是mapper[key]
和item[key]
都是union types 。 mapper[key]
的類型是((item: number) => string) | ((item: string) => string) | ((item: boolean)=>string)
((item: number) => string) | ((item: string) => string) | ((item: boolean)=>string)
((item: number) => string) | ((item: string) => string) | ((item: boolean)=>string)
並且item[key]
的類型是number | string | boolean
number | string | boolean
number | string | boolean
。 但是編譯器沒有很好的方法來跟蹤這些值的類型之間的相關性。 它將所有工會視為基本上彼此獨立。 我們知道mapper[key]
是(item: number) => string
當item[key]
是number
時,但是編譯器沒有。 據它所知, mapper[key]
可以接受一個number
,而item[key]
是string
。 這種相關性與我們在兩個表達式中使用相同的key
這一事實有關,但編譯器只跟蹤key
的類型,而不是它的標識。
當你獨立對待mapper[key]
和item[key]
時,你有點卡住了。 您只能調用 function 類型與 arguments 的聯合,這將適用於聯合的每個成員。 也就是說,參數的交集......所以編譯器將mapper[key]
視為可分配給(item: number & string & boolean) => string
,又名(item: never) => string
... 意思是這樣的function 通常不安全。
除非有某種方法可以告訴編譯器跟蹤聯合類型表達式之間的相關性,否則沒有很好的方法可以繼續。 如果您最關心類型安全,則可以編寫一些冗余代碼來獲得它:
data.map(item => keys.map(key =>
key === "A" ? mapper[key](item[key]) :
key === "B" ? mapper[key](item[key]) :
mapper[key](item[key])
)); // no error but it's redundant and repetitive
// and also redundant
如果您關心方便而不是類型安全,那么您可以使用類型斷言來抑制錯誤。 您的item[key] as never
示例是一種方法,盡管您在技術上對item[key]
是什么撒謊。 如果你不想撒謊,你可以像這樣使用通用回調 function :
data.map(item => keys.map(<K extends keyof Columns>(key: K) =>
(mapper[key] as (item: Columns[K]) => string)(item[key]) // okay
));
您必須斷言mapper[key]
是類型的值(item: Columns[K]) => string)
因為編譯器無法驗證這一點,即使理論上它應該能夠驗證。 當您嘗試調用它時,它會急切地將mapper[key]
解析為函數的聯合。 而且由於mapper[key]
確實是那種類型的值,我們沒有撒謊。 這里缺乏類型安全是因為如果有人邪惡地切換了mapper
的條目,編譯器不會注意到:
const evilMapper = {
A: mapper.B,
B: mapper.C,
C: mapper.A
}
data.map(item => keys.map(<K extends keyof Columns>(key: K) =>
(evilMapper[key] as (item: Columns[K]) => string)(item[key]) // okay?!
));
而冗余冗余的版本會開始尖叫:
data.map(item => keys.map(key =>
key === "A" ? evilMapper[key](item[key]) : // error
key === "B" ? evilMapper[key](item[key]) : // error
evilMapper[key](item[key]) // error
));
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.