TypeScript 数据映射器函数参数从不推断
Posted
技术标签:
【中文标题】TypeScript 数据映射器函数参数从不推断【英文标题】:TypeScript data mapper function argument infers never 【发布时间】:2022-01-14 19:21:09 【问题描述】:考虑以下示例:
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"
,这使得mapper[key]
减少为(item: number & string & boolean) => string
,即(item: never) => string
。 (如果我的概念是错误的,请指出。)
所以问题是,我怎样才能重写代码,使item[key]
不需要转换为never
?
【问题讨论】:
类型名称通常用 UpperPascalCase 编写,所以我在回答中将columns
切换为Columns
。此外,键类型的类型参数更传统地命名为K
(用于键)或P
(用于属性),因此我在答案代码中也将T in keyof columns
切换为K in keyof Columns
。
【参考方案1】:
这是 TypeScript 和 microsoft/TypeScript#30581 主题的一般限制。编译器确实无法查看像mapper[key](item[key])
这样的单个表达式并使用control flow analysis 对其进行分析以查看它是否安全。
问题是mapper[key]
和item[key]
都是union types。 mapper[key]
的类型是 ((item: number) => string) | ((item: string) => string) | ((item: boolean)=>string)
,item[key]
的类型是 number | string | boolean
。但是编译器没有很好的方法来跟踪这些值的类型之间的相关性。它将所有工会视为基本上彼此独立。我们知道mapper[key]
是(item: number) => string
恰好在item[key]
是number
时,但是编译器没有。据它所知,mapper[key]
可以接受number
,而item[key]
是string
。相关性与我们在两个表达式中使用相同的 key
的事实有关,但编译器只跟踪 key
的类型,而不是它的身份。
当您单独对待 mapper[key]
和 item[key]
时,您会陷入困境。您只能调用函数类型的联合with arguments that would work for every member of the union。也就是说,参数的intersection...所以编译器将mapper[key]
视为可分配给(item: number & string & boolean) => string
,也就是(item: never) => string
...意味着这样的函数通常不能安全调用。
除非有某种方法可以告诉编译器跟踪联合类型表达式之间的相关性,否则没有很好的方法可以继续。如果你最关心类型安全,你可以编写一些冗余代码来获得它:
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
如果您关心的是便利性而不是类型安全,那么您可以使用type assertion 来抑制错误。您的 item[key] as never
示例是一种方法,尽管您在技术上对 item[key]
是什么撒谎。如果你不想撒谎,你可以像这样使用generic 回调函数:
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
));
Playground link to code
【讨论】:
非常感谢您的详细回答!我知道在 *** 和官方 repo 中肯定有很多类似的问题和问题,但我不知道应该使用什么关键字来搜索它们。从你的链接中,我注意到昨天有一个 PR 旨在解决这个问题。很高兴知道这是 TypeScript 的限制,但不是我的错。再次非常感谢!以上是关于TypeScript 数据映射器函数参数从不推断的主要内容,如果未能解决你的问题,请参考以下文章
如何在返回其回调之一结果的函数的 Typescript 中声明类型?