Я использовал такой подход для условной передачи реквизита (в зависимости от другого реквизита) в реакции:
type CommonProps = { age: number };
type ConditionalProps =
| {
type: 'view' | 'add';
}
| {
type: 'edit';
initialData: number;
};
let Test = (props: CommonProps & ConditionalProps) => {
return <div />;
};
Моя цель была:
type
передается как «просмотреть» или «добавить», то я хочу, чтобы TS выдал ошибку, если я передам initialData
type
передается как «редактировать», то initialData
необходимо.На самом деле это работало большую часть времени, пока значение type не стало динамическим. Когда я передал значение type следующим образом:
<Test
age = {9}
type = {Math.random() > 0.5 ? 'edit' : 'view'}
initialData = {9}
/>
Теперь кажется, что это ошибка выше, потому что ТС больше не жалуется, и может случиться так, что type является «просмотром», и я также пропускаю initialData, что нарушает мою первую цель выше.
Я не уверен, может быть, я прошу слишком многого, но есть ли подход, который достигает двух вышеуказанных целей также и тогда, когда значение type является динамическим, как в этом примере?
Вы можете добиться этого с помощью Типов утилит TypeScript: Partial, Pick и Omit . Или вы можете прочитать соответствующее обсуждение здесь.
Решение @captain-yossarianfromUkraine из девиза ниже мне кажется более простым, есть ли у него какие-то недостатки, которых нет у вашего? (что я нашел немного странным в ответе девиза, так это то, что он дает ту же ошибку, даже если я использую string вместо never; я сам пробовал эту версию раньше, но из-за этого проигнорировал)
Решение девиза @GiorgiMoniava такое же, как у меня. Я предоставил вам общее решение для всех союзов и девиз предоставил вам пример для вашего конкретного случая. Смотрите результат StrictUnion, он также возвращает объединение, в котором запрещенным свойством является never
@captain-yossarianfromUkraine, когда вы связались здесь , я начал искать, точно ли это похоже на мой пример. Я думаю, что в основном это так, но, возможно, есть небольшая разница, потому что я использую размеченные союзы, а в этом примере нет. Например, смотрите здесь: t.ly/bo4y, вы видите, что test2 не ошибается, а test1 ошибается. Не могли бы вы вкратце объяснить, в чем там разница с точки зрения проверки типов? Или, может быть, я должен задать новый вопрос.
@GiorgiMoniava ты прав, это потому что {A:1, C: 3} is assignable to {C: number}. Союзы TS сложны. Вот почему я думаю, что использование дискриминационных союзов — самый безопасный вариант. Если вы не можете использовать дискриминатор, самым безопасным вариантом будет использование StrictUnion, где все запрещенные свойства never
@captain-yossarianfromUkraine да, но я имел в виду, у вас есть идеи, почему test1 ошибки в моей предыдущей ссылке?
@GiorgiMoniava это потому, что это другой случай. У этого типа объединения есть свой дискриминатор type. Учтите это. test1 оценивается как { type: "view" | "edit"; }, тогда как объект должен быть либо { type: 'view' }, либо { initialData: number; type: 'edit'; }. type не может быть в одном и том же типе ни view, ни edit, потому что тогда ТС не может понять, должен ли присутствовать initialData или нет. Если вы добавите initialData, это сработает. Представьте, что Math.random возвращает 0.6, тогда объект должен содержать initialData, но не будет
В одной из ссылок выше, демо TS, у меня был комментарий, в котором говорилось: «Здесь нет ошибки. Вероятно, потому, что {A:1, C: 3} присваивается {C: number}», что имхо не совсем верно. Причина в том, что свойство A существует в одном из объединений типа T2.
В общем, Typescript игнорирует лишние свойства, если только они не назначены явно или не переданы в качестве аргумента.
Итак, хотя в вашем примере вы передаете { age: number, type: "view", initialData: number }, это удовлетворяет типу { type: "view" | "add" }. Это похоже на то, как, например, вы можете игнорировать нерелевантные поля в данных из внешнего источника.
Однако, если вы действительно хотите запретить дополнительную поддержку в этом случае, вы можете сделать это, указав, что ее тип должен быть never:
type ConditionalProps =
| {
type: 'view' | 'add';
initialData?: never;
}
| {
type: 'edit';
initialData: number;
};
Обратите внимание, что мы указываем initialData как never, а также необязательно, так что его можно (должно) опускать.
С типом never Typescript сообщит об ошибке, если только реквизит type не будет "edit".
Минимальная копия доступна здесь
Дополнительный реквизит забанен даже без использования never, если я не передам type как Math.random() > 0.5 ? 'edit' : 'view' и просто использую «просмотр» или «редактирование». Так зачем мне использовать never?
@GiorgiMoniava Если вы добавите never, это запретит дополнительную поддержку
Это запрещает, даже если я использую скажем string вместо never: ссылка с аналогичным сообщением об ошибке
@GiorgiMoniava Я подключился к CodeSandbox, возможно, в вашем коде есть другие различия, которые не отражены в вашем вопросе.
Вы проверили мою ссылку выше? Это дает аналогичное сообщение с string вместо never
Но сообщение об ошибке ничего не говорит о initialData, оно говорит " Type '"view"' is not assignable to type '"edit"'. В любом случае, вы можете видеть, что в таком случае это работает без never: typescriptlang.org/play?#code/…
Давайте продолжим обсуждение в чате.
Пожалуйста, рассмотрите возможность использования помощника StrictUnion. Смотрите этот ответ и мою статью . Полный пример здесь