Объединение значений массива не работает должным образом

Сегодня я работал над методом, который создает карту ключ-значение из двух списков строк. Вот пример:

const keys = ['a', 'b', 'c']
const values = ['x', 'y', 'z']
const map = createMap(keys, values)
/*
{
  a: 'x',
  b: 'y',
  c: 'z'
}
*/

Реализация, на которую я попал, выглядит так:

function createMap<T extends string>(
  keys: readonly T[],
  values: readonly string[]
): Record<T, string> {
  if (keys.length !== values.length) {
    throw new Error('Key and Value lists must have same length') 
  }

  return keys.reduce<Record<string, string>>((accumulator, key, index) => {
    if (accumulator.hasOwnProperty(key)) {
      throw new Error('Keys must be unique')
    }

    return {
      ...accumulator,
      [key]: values[index]
    }
  }, {})
}

И это работает, но вывод типа имеет странное свойство

Если параметром key является переменная, которая содержит массив строк, результатом будет: Record<string, string> , но если вы непосредственно передадите массив параметру key , результат будет следующим: Record<'item1' | 'item2' | 'etc.', string> Record<'item1' | 'item2' | 'etc.', string> Record<'item1' | 'item2' | 'etc.', string> . Проверьте код ниже для более подробной информации:

const keys = ['a', 'b', 'c']
const values = ['x', 'y', 'z']
const map = createMap(keys, values) // type is Record<string, string>
const map = createMap(['a', 'b', 'c'], values) // type is Record<'a' | 'b' | 'c', string>

Может кто-нибудь объяснить, почему он так себя ведет?

Вот ссылка на этот код в TypeScript Playground

Всего 2 ответа


Это происходит потому, что тип литерала ['a', 'b', 'c'] выводится как тип кортежа ['a', 'b', 'c'] , который при использовании в качестве массива становится Array<'a' | 'b' | 'c'> Array<'a' | 'b' | 'c'> Array<'a' | 'b' | 'c'> . Напротив, ваша переменная keys подразумевает тип Array<string> .

Вы можете получить обратный результат с помощью явных аннотаций типов:

const keys: ['a', 'b', 'c'] = ['a', 'b', 'c']
const values = ['x', 'y', 'z']

const m1 = createMap(keys, values) // => Record<'a' | 'b' | 'c', string>
const m2 = createMap(['a', 'b', 'c'] as string[], values) // => Record<string, string>

В первом примере тип параметра keys имеет тип string[] и, следовательно, T имеет тип string поэтому возвращаемый тип становится Record<string, string> .

Во втором примере параметр keys является кортежем строковых литералов.

Смотрите документы о кортежах здесь: https://www.typescriptlang.org/docs/handbook/basic-types.html#tuple .

См. Документы о типах строковых литералов здесь: https://www.typescriptlang.org/docs/handbook/advanced-types.html#string-literal-types

Смотрите пример ниже для более ясного представления о том, что происходит:

const keys: ['a', 'b', 'c'] = ['a', 'b', 'c']; // Type is tuple of string literal types
const map = createMap(keys, values) // type is Record<'a' | 'b' | 'c', string>