«Динамический» тип при фильтрации ключей объектов

#typescript #api

Вопрос:

Допустим, у меня есть следующая полезная нагрузка:

{a: 1, b: 2, c: 3}

И я хочу иметь функцию, которая, учитывая объект, выбирает некоторые ключи и соответственно возвращает тип.

 function pick<T>(obj: any, ...keys: string[]): T {
  return (
    Object.fromEntries(
      keys
        .filter(key => key in obj)
        .map(key => [key, obj[key]])
    )
  ) as T;
}

const test = { a: 1, b: 2, c: 3 };

type Foo = {
  a: number,
  b: number
}
const result = pick<Foo>(test, 'a', 'b');
console.log(result);
 

Есть ли способ справиться с этим лучше? Я понимаю, что использование принуждения к типу в значительной степени отключает систему безопасности машинописи.

Например, если я сделаю это:

 console.log(pick<Foo>(test, 'b'));
 

Я говорю typescript, что функция pick возвращает объект Foo, но в нем не a определен ключ.

Вариант использования заключается в том, что у нас есть вызовы API, которые мы:

  1. Придется печатать
  2. Нужно только ввести соответствующие ключи для нашего варианта использования
  3. Сделайте это в соответствии с лучшими практиками TS

Ответ №1:

Если вас устраивает реализация pick без проверки типов, то вы можете, по крайней мере, выровнять все типы. Супер-удобный тип машинописи, который мы собираемся использовать для этого, на самом деле называется Pick .

 function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K> {
  return (
    Object.fromEntries(
      keys
        .filter(key => key in obj)
        .map(key => [key, obj[key]])
    )
  ) as any;
}
 

Реализация функции точно такая же, за исключением того , что в конце мы выполняем перебор any , что фактически говорит Typescript доверять нам. Pick<T, K> это именно тот тип , который вам нужен: в нем говорится: «возьмите тип T , подпишите его типом K и посмотрите, что мы получим». K является произвольным типом, который расширяется keyof T . Предполагая, что вы передадите постоянные аргументы этой функции, вы получите объединение литералов типа "a" | "b" for K .

Я не знаю способа Object.fromEntries проверить тип, если не считать перебора any , как мы делали выше. По моему опыту, когда вы начинаете возиться с функциями на Object прототипе, вам нужно немного подтолкнуть машинопись, чтобы получить правильные типы для вещей.