компонент, отображающий различные типы вопросов сбой типа guard

#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;
  }
};
 

Stackblitz