Это заметка про тайпскрипт, и опытные разработчики не узнают что-то новое, но зато вот URL, на который можно ссылаться.
Скажем, у нас есть интерфейс, который используется и для ошибок, и для предупреждений:
interface NotificationProps {
type: 'error' | 'warning'
message: string
}
И добавляется новое требование, что в нотификашке предупреждений должна быть кнопка «Исправить» с настраиваемым текстом.
Наивный способ — расширить интерфейс опциональными свойствами:
interface NotificationProps {
type: 'error' | 'warning'
message: string
buttonText?: string
buttonAction?: () => void
}
Это меньше кода менять, интерфейс уже объявлен, просто докинули свойство и заиспользовали там, где показываем предупреждения, да? Нет.
У интерфейса «до» было всего два валидных варианта:
{ type: "error", message }
{ type: "warning", message }
Добавив два опциональных свойства, мы создали 8 возможных комбинаций:
- error + buttonText + buttonAction
- error + no buttonText + buttonAction
- error + buttonText + no buttonAction
- error + no buttonText + no buttonAction
- warning + buttonText + buttonAction
- warning + no buttonText + buttonAction
- warning + buttonText + no buttonAction
- warning + no buttonText + no buttonAction
Каждое опциональное свойство удваивает количество разрешенных состояний.
И большая часть этих состояний нарушает требования.
Вполне возможно, что конкретный код, который есть в кодовой базе сейчас, не попадает в неправильные состояния! Но это не гарантирует, что такого не произойдёт в будущем. Мы держим в голове, какие состояния правильные, а какие нет, и тайпскрипт нам не сможет помочь — это не очень надёжно.
Решение
Вариант получше с точки зрения долгосрочной надёжности: использовать юнионы, чтобы описать два конкретных правильных состояния:
type NotificationProps = { type: 'error', message: string } | {
type: 'warning',
message: string,
buttonText: string,
buttonAction: () => void
}
Можно даже вынести общую часть, но пока кажется оверкиллом:
type NotificationProps = { message: string } & (
| { type: 'error' }
| { type: 'warning', buttonText: string, buttonAction: () => void }
)
TS Playground поиграть.