Какие типы данных учитывать для стейтов и как правильно типизировать данные и ошибки
Что проверяют
Умеете ли вы описать состояние так, чтобы невозможные комбинации отсекались на уровне типов, и что делаете с ошибкой, тип которой заранее неизвестен. Следом обычно просят показать, как типизировать ответ API, которому нельзя слепо доверять.
Как отвечать
Главный приём для асинхронного состояния: размеченное объединение вместо набора флагов.
type State<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: AppError };Три флага isLoading, isError, isSuccess дают восемь комбинаций, из которых осмысленны три или четыре. Объединение по полю status оставляет только допустимые, а после проверки state.status === "success" TypeScript сам сужает тип и открывает data. В switch по статусу добавьте ветку default с присваиванием в never: забытый новый статус станет ошибкой компиляции.
Про ошибки:
catchполучаетunknown: приstrictвключёнuseUnknownInCatchVariables, иerror.messageбез проверки не скомпилируется. Сужайте черезinstanceof Errorили свой type guard;- свой тип ошибки: например объединение по полю
kindс вариантамиnetwork,httpсоstatusиvalidationсо списком полей. UI решает поkind, а не парсит текст; - ответ сервера:
as User[]ничего не проверяет. На границе нужна схема, напримерzod, а тип выводится из неё черезz.infer.
В useState указывайте тип явно, когда начальное значение его не выражает: useState<User | null>(null). Статусы удобнее держать объединением строковых литералов, а не enum: оно не порождает кода в рантайме.
Где спотыкаются
Хранят { hasError: boolean; message?: string }. Такой тип разрешает hasError: false вместе с текстом ошибки, и компонент показывает то, чего нет.
Пишут catch (e: any) и дальше обращаются к полям наугад. Выброшено может быть что угодно, включая строку или undefined.
Кладут в стейт Date, пришедший из JSON. После JSON.parse там строка, и тип начинает врать.