У меня есть функция TypeScript useSome, которая использует дженерики для определения объекта params на основе заданного ключа или набора ключей:
const useSome = <Keys extends string | undefined>() => {
const params = {} as Keys extends string ? Record<Keys, string> : Partial<Record<string, string>>;
return params;
};
Вот как я хочу использовать эту функцию:
const { id } = useSome(); // Expected: id is of type string | undefined
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, чтобы она корректно работала с несколькими клавишами? Можно ли добиться желаемого поведения при передаче нескольких ключей в универсальном типе?
@jcalz спасибо за ответ. Второй способ - это именно то, что мне нужно, и вы можете написать его как ответ, и я отмечу его как правильный. Если возможно, можете ли вы прикрепить к вашему ответу несколько статей, где я могу прочитать об этих string extends
случаях?
Если я правильно понимаю ваш вопрос, вы хотите, чтобы функция 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
Надеюсь, это поможет!
спасибо за ответ, но я не хочу передавать в эту функцию какие-либо аргументы
Предостережение: мне не ясно, почему вы хотите 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;
}*/
Выглядит хорошо.
Меня смущает, почему вы исключаете
undefined
, когда звонитеuseSome<'id' | 'type'>()
; Чтобы разрешить тамundefined
(это единственное, что имеет смысл, если вы вызываетеuseSome()
без аргументов времени выполнения), вы можете сделать это таким образом . Если вам действительно нужно попытаться провести это различие, вы можете сделать это вот так. Это полностью решает вопрос? Если да, то я напишу ответ. Если нет, то что мне не хватает?