Универсальный ввод, динамически выводимый в типах потока

#javascript #generics #flowtype

#javascript #универсальные типы #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 тест

Ответ №1:

Для этого вы хотите использовать $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 . Но если вы хотите получить тип свойства объекта на основе некоторого произвольного строкового типа, такого как generic, вам нужно $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;
}
  

Вроде бы кажется, что так и должно быть, но это не так. Не уверен, что это что-то специфичное для flow или я просто недостаточно знаю о системах типов в целом.