Как определить сопоставленный тип с необязательным подмножеством ключей в TypeScript?

У меня есть код TypeScript, который аккуратно сопоставляет типы с их возможными значениями:

export const randomMappings = {
  coin: ['heads', 'tails'],
  d4: [1, 2, 3, 4],
  d6: [1, 2, 3, 4, 5, 6],
  d10: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
} as const;

export type RandomType = keyof typeof randomMappings;
export type RandomValue<T extends RandomType> = typeof randomMappings[T][number];
// RandomValue<'coin'> is 'heads'|'tails'; RandomValue<'d4'> is 1|2|3|4 and so on.
export type RandomResults = {
  [T in RandomType]?: RandomValue<T>;
};


export function pickRandomValues(types: RandomType[]): RandomResults {
  return Object.fromEntries(
    types.map((type) => [
      type,
      randomMappings[type][Math.floor(Math.random() * randomMappings[type].length)],
    ]),
  );
}

Я хотел бы обеспечить, чтобы тип, возвращаемый pickRandomValues, был картой, которая содержала только ключи, представленные в аргументе types, вместо того, чтобы иметь все возможные ключи как необязательные.

Например:

const x = pickRandomValues(['coin', 'd6']);
console.log(x.coin, x.d6); //works
console.log(x.d10); //should fail because d10 is not present in x

Я думаю, что способ определения RandomValue<T> довольно элегантен, поэтому я не хотел бы его менять, если это возможно.

Есть ли способ сделать это? Я был бы не против изменить аргумент types на карту вместо массива, чтобы я мог сделать что-то вроде {[T in keyof typeof types]: RandomValue<T>}, но то, что я пробовал, не работает, потому что, если я хочу, чтобы он позволял передавать объект только с интересующими меня ключами тип будет таким же, как сейчас RandomResults (все ключи необязательны).


person fortran    schedule 16.06.2021    source источник


Ответы (1)


Вы можете сделать pickRandomValues общим для значений в types:

export function pickRandomValues<T extends RandomType>(
  types: T[]
): {[K in T]: RandomValue<K>} {
  return Object.fromEntries(
    types.map((type) => [
      type,
      randomMappings[type][Math.floor(Math.random() * randomMappings[type].length)],
    ]),
    // needs type assertion
  ) as {[K in T]: RandomValue<K>};
}

const coin = pickRandomValues(['coin'])
coin.coin // 'heads' | 'tails'
coin.d4 // error

const coinD4 = pickRandomValues(['coin', 'd4'])
coinD4.coin // 'heads' | 'tails'
coinD4.d4 // 1 | 2 | 3 | 4
coinD4.d10 // error

Например, в pickRandomValues(['coin', 'd4']) T будет 'coin' | 'd4', а тип возврата будет {coin: RandomValue<'coin'>, d4: RandomValue<'d4'>}.

площадка ссылка

person cherryblossom    schedule 16.06.2021