#typescript #overloading
Вопрос:
Это код, с которым я борюсь:
const enum labelKey {
RATING = 'asset.ratingPercentage',
EPISODE_TITLE = 'episode.title.count.short',
EPISODE_SERIES_TITLE = 'episode.title.series.count.short',
}
interface GetLabel {
(key: labelKey.RATING, options: { rating: string }): string;
(
key: labelKey.EPISODE_TITLE,
options: { episodeNumber: number; seasonNumber: number; episodeTitle: string },
): string;
(
key: labelKey.EPISODE_SERIES_TITLE,
options: { episodeNumber: number; seasonNumber: number; seriesName: string },
): string;
(key: string): string;
}
const getLabel: GetLabel = (key: string, options?: { [key: string]: unknown }) => {
return '';
};
type Label = {
key: labelKey.RATING;
options: { rating: string };
} | {
key: labelKey.EPISODE_TITLE;
options: { episodeNumber: number; seasonNumber: number; episodeTitle: string };
} | {
key: labelKey.EPISODE_SERIES_TITLE;
options: { episodeNumber: number; seasonNumber: number; seriesName: string };
};
function createLabel(someCondition: boolean): Label {
if (someCondition) {
return {
key: labelKey.RATING,
options: { rating: '5' },
};
}
else {
return {
key: labelKey.EPISODE_TITLE,
options: { episodeNumber: 1, seasonNumber: 2, episodeTitle: 'Hello' },
};
}
}
const label = createLabel(false);
getLabel(label.key, label.options);
Я даже добавил labelKey
перечисление (которое, я думаю, не должно быть необходимым), чтобы дать подсказку машинописному тексту, но getLabel
все равно не работает.
Есть какие-нибудь идеи о том, как этого можно добиться?
Вот и Игровая площадка.
Ответ №1:
Корень проблемы в том, что вы не знаете тип label.key
в тот момент, когда звоните getLabel
. Таким образом, ни одна из первых трех перегрузок не может быть сопоставлена, поскольку все они требуют определенных типов ключей. Но вы также не можете сопоставить четвертую string
перегрузку, потому что эта перегрузка (key: string): string;
требует, чтобы вы не передавали второй аргумент.
Решение 1: getLabel
Давайте добавим перегрузку, когда мы знаем, что у нас есть labelKey
, но не знаем, какая именно. Поэтому мы должны принять options
для любой из перегрузок, не требуя, чтобы тип параметров соответствовал типу ключа. Затем в качестве последнего средства вы можете добавить еще одну перегрузку, которая принимает любой string
и любой options
объект.
interface GetLabel {
...exisiting overloads 1-3...
(key: Label['key'], options: Label['options']): string;
(key: string, options: Record<string, any>): string;
}
Решение 2: createLabel
Но мы также можем решить эту проблему с createLabel
помощью функции. В настоящее время Typescript не знает, какой тип Label
возвращает эта функция. Но мы должны быть в состоянии знать, что Label
мы получим, основываясь на значении someCondition
, с которым мы вызываем функцию. Так что мы действительно можем добавить сюда перегрузки. Примечание: на самом деле вам это не нужно as const
, если у вас есть явный тип возврата.
// helper function to get a member of the `Label` union
type LabelByKey<K extends labelKey> = Extract<Label, {key: K}>
function createLabel(someCondition: true): LabelByKey<labelKey.RATING>;
function createLabel(someCondition: false): LabelByKey<labelKey.EPISODE_TITLE>;
function createLabel(someCondition: boolean): Label {
if (someCondition) {
return {
key: labelKey.RATING,
options: { rating: '5' },
}
}
else {
return {
key: labelKey.EPISODE_TITLE,
options: { episodeNumber: 1, seasonNumber: 2, episodeTitle: 'Hello' },
}
}
}
Теперь, когда вы звоните createLabel(false)
, вы знаете, что получаете ярлык, тип которого выглядит так:
const label: {
key: labelKey.EPISODE_TITLE;
options: {
episodeNumber: number;
seasonNumber: number;
episodeTitle: string;
};
}
Нам не нужны эти дополнительные перегрузки, потому что теперь мы можем сопоставить вторую перегрузку key: labelKey.EPISODE_TITLE
.
Ссылка на игровую площадку для машинописи
Решение 3: Дженерики
Информация, необходимая для сопоставления a labelKey
с its options
, уже доступна в Label
союзе. Так что вам не нужно вводить его снова при перегрузках. Мы можем использовать универсальную функцию вместо перегруженной и использовать тот же LabelByKey
вспомогательный тип.
type LabelByKey<K extends labelKey> = Extract<Label, {key: K}>
const getLabel = <K extends labelKey>(
key: K,
options: LabelByKey<K>['options']
) => {
return '';
};
Комментарии:
1. Таким образом, хитрость заключается в том, чтобы не использовать перегруженную функцию, а вместо этого использовать
Label
объединение, а затем некоторую магию TS, чтобы извлечь соответствующую метку из этого объединения. Спасибо, Линда! Это довольно исчерпывающий ответ.
Ответ №2:
если вы измените определение label
на
const label = {
key: labelKey.RATING,
options: { rating: '5' },
} as const;
предполагаемый тип key
свойства будет labelKey.RATING
вместо labelKey
, и вы не получите эту ошибку
Вот детская площадка
—
Изменить: вы можете заменить свои перегрузки с помощью универсальных средств, основанных на определении Label
interface GetLabel {
<K extends labelKey>(key: K, options: Extract<Label, {key: K}>['options']): string;
(key: string): string;
}
Комментарии:
1. Спасибо за ответ Тенефф! К сожалению, мой пример был недостаточно реалистичным, мне не хватало функции. Теперь я обновил свой вопрос. Добавление
const
в каждую ветвь этой функции, похоже, не работает. Ты видишь выход из этого?2. @Ashitaka Я обновил свой ответ и заметил, что он в значительной степени совпадает с приведенным ниже