[英]How to properly type function that convert array of objects into object of objects
我正在使用.reduce
尝试这种语法:
function arrToObject<T: {key: string}, R: {[string]: T}>(list: Array<T>): R {
return list.reduce((result: R, item: T): R => {
result[item.key] = item;
return result;
}, {});
}
但是 Flow 给出了以下错误:
call of method `reduce`. Function cannot be called on any member of intersection type
根本问题在于协调泛型类型R
与{}
,即累加器的初始值。
一个 hacky 修复会阻止 Flow 尝试协调类型:
function arrToObject<T: {key: string}, R: {[string]: T}>(list: Array<T>): R {
var accum: any = {};
return list.reduce((result: R, item: T): R => {
result[item.key] = item;
return result;
}, accum);
}
但是这存在一些问题,我们将在下面进一步了解。 当我们明确说明累加器是什么类型时,事情变得更清楚了:
function arrToObject<T: {key: string}, R: {[string]: T}>(list: Array<T>): R {
let accum: R = {};
return list.reduce((result: R, item: T): R => {
result[item.key] = item;
return result;
}, accum);
}
这迫使有问题的类型协调成为一个更简单的语句,周围没有太多噪音。 它会产生更好的错误:
3: let accum: R = {};
^ object literal. This type is incompatible with
3: let accum: R = {};
^ some incompatible instantiation of `R`
这个错误并不能说明全部情况,但它使我们更接近真实情况。
Flow 要求泛型参数R
可以是与约束兼容的任何类型。 因此, R
有效实例可能比您对其施加的约束更通用,例如具有额外的必填字段。
这就产生了一个问题。 在函数体内部,您不可能知道R
的实际实例化是什么样的。 所以你不能构造一个。 尽管错误消息很糟糕,但 Flow 是正确的,可以阻止您这样做! 如果有人使用R
实例化为具有额外字段的内容调用您的函数,例如{[string]: {key: string}, selected: boolean}
怎么办? 然后,您必须以某种方式知道用selected: boolean
字段初始化累加器。
如果可以,比上述 hack 更好的解决方案是完全删除泛型:
type KeyMap = {[string]: {key: string}};
type Item = {key: string};
function arrToObject(list: Array<Item>): KeyMap {
return list.reduce((result:KeyMap, item: Item): KeyMap => {
result[item.key] = item;
return result;
}, {});
}
我还引入了一些类型别名来防止函数签名失控。
现在泛型被具体类型所取代,事情变得简单多了。 函数体可以为累加器创建一个新的初始值,因为它的确切类型是已知的。
如果出于某种原因确实需要泛型类型参数,也可以通过将累加器的初始值作为另一个参数传递给arrToObject
来arrToObject
。 这将起作用,因为您的arrToObject
的调用者将知道具体类型并能够实例化它。
@PeterHall 的回答正确地确定了类型检查失败的原因,但解决方案不是最佳的。 正如答案所说,问题在于R
可能是满足{[string]: T}
任何类型,但实际上所需的正是该类型。 相反,需要一个泛型类型参数保留项目类型时项目进入和流出的arrToObject
功能,具有{key: string}
是的输入时所需的最低签名。
此签名有效:
function arrToObject<T: {key: string}>(list: Array<T>): {[string]: T}
或者,使用命名类型:
type KeyMap<T> = {[string]: T};
type Item = {key: string};
function arrToObject<T: Item>(list: Array<T>): KeyMap<T>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.