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

У меня есть функция TypeScript useSome, которая использует дженерики для определения объекта params на основе заданного ключа или набора ключей:

const useSome = <Keys extends string | undefined>() => {
  const params = {} as Keys extends string ? Record<Keys, string> : Partial<Record<string, string>>;

  return params;
};

Вот как я хочу использовать эту функцию:

  1. Пример одного ключа:
const { id } = useSome(); // Expected: id is of type string | undefined
  1. Пример нескольких ключей:
const { id, type } = useSome<'id' | 'type'>(); // Expected: id and type are both of type string

Однако, когда я предоставляю несколько ключей в общем коде, я получаю следующую ошибку:

Property 'id' does not exist on type 'Record<"id", string> | Record<"type", string>'.

Вот ссылка TypeScript's Playground, чтобы продемонстрировать проблему.

Мои вопросы:

Как изменить функцию useSome, чтобы она корректно работала с несколькими клавишами? Можно ли добиться желаемого поведения при передаче нескольких ключей в универсальном типе?

Меня смущает, почему вы исключаете undefined, когда звоните useSome<'id' | 'type'>(); Чтобы разрешить там undefined (это единственное, что имеет смысл, если вы вызываете useSome() без аргументов времени выполнения), вы можете сделать это таким образом . Если вам действительно нужно попытаться провести это различие, вы можете сделать это вот так. Это полностью решает вопрос? Если да, то я напишу ответ. Если нет, то что мне не хватает?

jcalz 02.09.2024 22:37

@jcalz спасибо за ответ. Второй способ - это именно то, что мне нужно, и вы можете написать его как ответ, и я отмечу его как правильный. Если возможно, можете ли вы прикрепить к вашему ответу несколько статей, где я могу прочитать об этих string extends случаях?

J. Doe 03.09.2024 12:45
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
0
2
50
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Попробуйте это:

const useSome = <Keys extends string | undefined>(...keys: Keys[]): Record<Extract<Keys, string>, string> => {
  const params = {} as Record<Extract<Keys, string>, string>;

  // Example of how you might populate `params` with some values.

  keys.forEach(key => {
    params[key] = "some value";
  });

  return params;
}

// Usage examples
const { id } = useSome('id'); // type of id is string

const { id, type } = useSome('id', 'type'); // type of id is string, type of type is string

Надеюсь, это поможет!

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

J. Doe 03.09.2024 12:47
Ответ принят как подходящий

Предостережение: мне не ясно, почему вы хотите useSome<'id'>() создать значение типа {id: string}, а не {id?: string}, поскольку во время выполнения вы просто вызываете useSome(), и нет никакого способа гарантировать, что ключи, указанные разработчиком TS, действительно будут существовать. . По сути, это похоже на использование утверждения типа , но оно скрыто внутри реализации вашей функции. Если вы собираетесь это сделать, я бы порекомендовал просто попросить людей написать useSome() as {id: string} и четко указать на отсутствие типовой безопасности. Однако с этого момента я буду предполагать, что у вас есть причина, по которой useSome() должен производить {[k: string]: string | undefined}, а useSome<'id'> должен производить {id: string}.


Я бы хотел определить ваши типы следующим образом:

const useSome = <K extends string>() => {
  const params = {} as string extends K ? { [P in K]?: string } : { [P in K]: string }    
  return params;
}

Если вы вызовете useSome() без указания вручную аргумента общего типа ️ для K, TypeScript не сможет вывести его и вернется к ограничению string. С другой стороны, если вы вызовете useSome() и укажете строку литерального типа или объединение таких типов, как useSome<'x'|'y'>(), то K будет уже, чем string.

Таким образом, мы можем просто проверить, является ли K уже string или нет. Мы это знаем K extends string, но нам нужна обратная проверка. Условный тип string extends K ? ⋯ : ⋯ будет истинным, если K равен в точности string (или шире, но не может быть шире из-за ограничения) и ложным, если K является некоторым типом строкового литерала или объединением таких типов.

Итак, если string extends K истинно, то нам нужны дополнительные свойства, как в {[P in K]?: string}, которое оценивается как {[k: string]: string | undefined} (поскольку отображаемый тип поверх string создает строку сигнатуру индекса , а модификатор сопоставления ? просто добавляет undefined к индексу свойство подписи, поскольку необязательных подписей индекса нет). В противном случае нам нужны требуемые свойства, как в {[P in K]: string}.

Давайте проверим это:

const { id, type } = useSome<'id' | 'type'>()
//      ^? const id: string
/* const useSome: <"id" | "type">() => {
    id: string;
    type: string;
} */

const { id2 } = useSome()
//      ^? const id2: string | undefined
/* const useSome: <string>() => {
    [x: string]: string | undefined;
}*/

Выглядит хорошо.

Детская площадка, ссылка на код

Другие вопросы по теме