Создайте тип ключа/значения из типа объекта

#typescript #typescript-generics

Вопрос:

Допустим, у нас есть тип

 type Foo = {
  a: string;
  b: number;
  c: boolean;
}
 

Теперь я хочу определить тип для объекта , имеющего a key и a value заданного типа T , чтобы тип значения выводился непосредственно из ключа, чтобы я мог сделать:

 const kv1: KeyValue<Foo> = {key: 'a', value: 'STRING'};
const kv2: KeyValue<Foo> = {key: 'b', value: 42};
const kv3: KeyValue<Foo> = {key: 'c', value: true};
 

Если я просто сделаю это:

 type KeyValue<T> = {
  key: keyof T;
  value: T[keyof T]
}
 

… тогда, очевидно, значения будут представлять собой объединение значений всех свойств в Foo :

И если я сделаю это:

 type KeyValue<T, K extends keyof T> = {
  key: K;
  value: T[K]
}
 

… тогда конечно, если явно набирается как KeyValue<Foo, 'a'> я могу создать объект литералы, которые соответствуют типу Foo :s свойства, а если я не конкретно указать тип и ключ для каждого литерала, а просто делать KeyValue<Foo, keyof Foo> , каждое значение будет позволено иметь тип любого значения Foo , то есть, все значения будут string | number | boolean вместо того, чтобы вывести из введенных key .

В конце концов, я хочу иметь возможность делать такие вещи, как это:

 const kvs: Array<KeyValue<Foo>> = [
  {key: 'a', value: 'STRING'}, // should not compile if value is a number or boolean
  {key: 'b', value: 42}, // should not compile if value is a string or boolean
  {key: 'c', value: true}, // should not compile if value is a string or number
];
 

Можно ли вывести KeyValue тип с этими ограничениями, чтобы он эффективно стал KeyValue<Foo, 'a'> | KeyValue<Foo, 'b'> | KeyValue<Foo, 'c'> во втором примере выше, без необходимости вручную писать это объединение? В принципе, я предполагаю, что я хотел бы иметь возможность определить тип value из значения key в текущем литеральном объекте.

Ответ №1:

Вы не можете представлять каждую пару ключ-значение с помощью одного KeyValue объекта, так как это потребовало бы либо частичного вывода (для ключа), либо какой-либо ссылки на key свойство из value свойства внутри одного и того же объекта.

Однако то, что вы могли бы сделать, — это создать дискриминируемое объединение с использованием объекта ввода.

 type Foo = {
  a: string;
  b: number;
  c: boolean;
}

type KeyValue<T> = {
  [P in keyof T]: {
    key: P;
    value: T[P];
  }
}[keyof T];

// with KeyValue<Foo>, this generates the following type
// 
// {
//     key: "a";
//     value: string;
// } | {
//     key: "b";
//     value: number;
// } | {
//     key: "c";
//     value: boolean;
// }

const kvs: Array<KeyValue<Foo>> = [
  {key: 'a', value: 40}, // ERROR
  {key: 'b', value: true}, // ERROR
  {key: 'c', value: "foo"}, // ERROR
];
 

Комментарии:

1. Черт, вот это умно! Слава!