我发现很难描述这个问题,这可能就是我无法找到解决方案的原因。但是我有一个基本上将对象和对象数组转换为地图的函数。其中某个(字符串)键作为标识符给出。
现在我正在尝试为该函数添加输入:
function mapFromObjects<K, T: {[string]: mixed}>(objects: $ReadOnlyArray<T>, key: string): Map<K, T> {
const m: Map<K, T> = new Map();
for (const item of objects) {
const keyval = item[key];
m.set(keyval, item);
}
return m;
}
现在这个函数给出了错误
Cannot call `m.set` with `keyval` bound to `key` because mixed [1] is incompatible with `K` [2]
所以我必须检查/限制泛型类型T.我尝试这样做:
function mapFromObjects<K, T: {[string]: K}>(objects: $ReadOnlyArray<T>, key: string): Map<K, T> {
const m: Map<K, T> = new Map();
for (const item of objects) {
const keyval = item[key];
m.set(keyval, item);
}
return m;
}
但是,如果我现在使用此功能,如:
type dataTy = {
id: number,
hidden: boolean,
}
const data_array: Array<dataTy> = [
{id: 0, hidden: true},
]
mapFromObjects(data_array, 'id');
弹出以下错误:
Cannot call `mapFromObjects` with `data_array` bound to `objects` because number [1] is incompatible with boolean [2] in property `id` of array element.
这也不是未预料到的。
现在我“期待”两个错误,但是我无法“解决”它。该函数本身工作正常,我只是坚持如何正确编写函数的类型。 (除了T: {[string]: any}
)。
你想使用$ElementType
。
function mapFromObjects<T: {+[string]: mixed}, S: $Keys<T>>(
objects: $ReadOnlyArray<T>,
key: S,
): Map<$ElementType<T, S>, T> {
const m = new Map<$ElementType<T, S>, T>();
for (const item of objects) {
const keyval = item[key];
m.set(keyval, item);
}
return m;
}
type dataTy = {
id: number,
hidden: boolean,
}
const data_array: Array<dataTy> = [
{id: 0, hidden: true},
]
const res: Map<number, dataTy> = mapFromObjects(data_array, 'id');
// const res: Map<boolean, dataTy> = mapFromObjects(data_array, 'id'); // error
const keys: number[] = Array.from(res.keys());
//const keys: string[] = Array.from(res.keys()); // error
首先要知道的是,如果上面例子中的'id'
字符串以任何方式计算,这将不起作用。就像你正在进行某种字符串操作来获取索引字符串一样,你运气不好。
第二件要知道的是,如果你想根据字符串文字得到一个对象属性的类型,你应该使用$PropertyType
。但是如果你想根据一些像泛型这样的任意字符串类型来获取对象属性的类型,你需要$ElementType
。
我还没有想到的一个挑剔的事情是把它放在一个泛型的返回值中。例如,这不起作用:
function mapFromObjects<T: {+[string]: mixed}, S: $Keys<T>, K: $ElementType<T, S>>(
objects: $ReadOnlyArray<T>,
key: S,
): Map<K, T> {
const m = new Map<K, T>();
for (const item of objects) {
const keyval = item[key];
m.set(keyval, item);
}
return m;
}
它有点像应该感觉,但事实并非如此。不确定这是否特定于流程或者我对一般的类型系统不够了解。