通用类型,以流类型动态派生

时间:2019-03-14 22:24:56

标签: javascript generics flowtype

我发现很难描述问题,这很可能就是为什么我找不到解决方案。但是我有一个函数,它基本上将对象的转换和数组转换为地图。在给定某些(字符串)键作为标识符的地方。

现在,我正在尝试向该函数添加键入内容:

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}之外。)

flow try test

1 个答案:

答案 0 :(得分:0)

您要为此使用$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'字符串是以任何方式计算的,则将无法使用。就像您在进行某种字符串操作以获取索引字符串一样,您很走运。

第二件事是,如果要基于字符串 literal 获取对象属性的类型,则应使用$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;
}

有点像应该的,但事实并非如此。不知道这是否是特定于流的东西,或者我只是对类型系统一般不了解。