#reactjs #typescript
#reactjs #typescript
Вопрос:
Мой тип guard — это значение type
свойства в вопросе. Например, radio
vs text
:
interface BaseQuestionType {
label?: string
}
export type RadioQuestionType = BaseQuestionType amp; {
type: 'radio'
}
export type TextQuestionType = BaseQuestionType amp; {
type: 'text' | 'textarea'
}
export type QuestionType =
| RadioQuestionType
| TextQuestionType
При его использовании Typescript жалуется, что RadioQuestionType
ему присваивается TextQuestionType
. Я бы хотел, чтобы он обрабатывал передачу any QuestionType
.
<Question
question={QUESTION_MAP[currentQuestionId] as QuestionType} // ERROR HIGHLIGHTS THIS LINE
/>
// ...
interface BaseQuestionPropsType<TAnswerMap> {
question: QuestionType
}
type TextQuestionPropsType<TAnswerMap> = BaseQuestionPropsType<TAnswerMap> amp; {
question: TextQuestionType
}
type RadioQuestionPropsType<TAnswerMap> = BaseQuestionPropsType<TAnswerMap> amp; {
question: RadioQuestionType
}
type PropsType<TAnswerMap> =
| RadioQuestionPropsType<TAnswerMap>
| TextQuestionPropsType<TAnswerMap>
type TAnswerMap = Record<string, unknown>
const Question: React.FC<PropsType<TAnswerMap>> = props => { /* ... */ }
Вот полная ошибка Typescript:
Types of property 'question' are incompatible.
Type 'QuestionType' is not assignable to type 'TextQuestionType'.
Type 'RadioQuestionType' is not assignable to type 'TextQuestionType'.
Type 'RadioQuestionType' is not assignable to type '{ type: "textarea" | "text"; }'.
Types of property 'type' are incompatible.
Type '"radio"' is not assignable to type '"textarea" | "text"'.ts(2322)
Комментарии:
1. Может быть потому
type
, что это зарезервированное ключевое слово в TypeScript, и вы используете его как свойство. Можете ли вы попробовать изменить это на что-то другое, напримерtyp
?2. Нет, проблема не в этом. @CasDekkers
Ответ №1:
Проблема заключается в PropsType<TAnswerMap>
интерфейсе. В вашем компоненте вопроса вы можете использовать это вместо:
const Question: React.FC<BaseQuestionPropsType<TAnswerMap>> = props => { /* ... */ }
Он BaseQuestionPropsType
уже размещен QuestionType
в question
поле.
Компилятор может выбирать между 2 типами в объединении только в том случае, если существует уникальный «буквальный» тип для их различения. В противном случае он не сможет этого сделать, и вы должны разместить все типы в объединении. Прочитайте различающие объединения из документации
Ответ №2:
Здесь у вас перегрузка для типа объединения в том смысле, что проверка на questionType не является уникальной! Объект типа BaseQuestionType
может в то же время быть объектом типа RadioQuestionType
или TextQuestionType
.
Вы могли бы определить:
interface BaseQuestionType {
label?: string;
type: never;
}
если вы хотите исправить это, сохраняя при этом заданную структуру. Однако вопросы либо относятся к типу RadioQuestionType
, либо TextQuestionType
больше не будут относиться к типу BaseQuestionType
, а это то, что вам нужно для защиты безопасного типа.
Ответ №3:
Похоже, что проблема заключается в приведении типа ( as QuestionType
). Я воссоздал ваш пример в: typescript playground
// works
Question({ question: { type: 'text' }})
// works
const q: QuestionType = { type: 'text' }
Question({ question: q })
// doesn't work
const q2 = { type: 'text' } as QuestionType
Question({ question: q2 })
Я не знаю, что у вас есть в вашем QUESTION_MAP
или почему приведение типов необходимо, но, похоже, это основная причина вашей проблемы.
Ответ №4:
Просто чтобы добавить к ответу Хассана Накви,
Проблема заключается в определении типа компонента. PropsType<TAnswerMap>
Вы бы не пытались сопоставить входящее определение с другим определением.
Тип объединения QuestionType
уже делает это. Вы говорите , что тип вопроса имеет форму RadioQuestionType
ИЛИ TextQuestionType
.
Компонент вопроса в основном ожидает одно question
входное свойство типа QuestionType
.
Таким образом, ваши модели полей ввода могут быть написаны следующим образом:
interface BaseQuestionType {
label?: string;
}
export interface RadioQuestionType extends BaseQuestionType {
type: "radio";
}
export interface TextQuestionType extends BaseQuestionType {
type: "text" | "textarea";
}
export type QuestionType = RadioQuestionType | TextQuestionType;
И компонент вопроса может иметь только один интерфейс для ожидаемых входных реквизитов.
interface QuestionProps {
question: QuestionType;
}
const Question: React.FC<QuestionProps> = props => {
// This is how you could discriminate the type:
switch (props?.question?.type) {
case 'textarea':
case 'text':
return <h1>{props?.question?.type} Type!</h1>;
case 'radio':
return <h1>{props?.question?.type} Type!</h1>;
default:
break;
}
};