Как реализовать динамически настраиваемое поле в интерфейсе TypeScript
Что проверяют
Умеете ли вы описать поле, форма которого зависит от настройки, так, чтобы компилятор ловил ошибки, а не пропускал всё через any. Типичное уточнение: как вывести тип значений формы прямо из конфигурации полей.
Как отвечать
Уточните, что значит «динамическое»: набор ключей неизвестен заранее или тип значения зависит от вида поля. Под это разные инструменты:
- дискриминированное объединение:
{ kind: "text"; value: string } | { kind: "number"; value: number } | { kind: "select"; value: string; options: string[] }. После проверкиfield.kindTypeScript сужает тип, иoptionsдоступен только уselect; - дженерик:
interface Field<T> { name: string; value: T; validate?: (v: T) => string | null }, когда тип значения задаёт вызывающий код; - индексная сигнатура и Record: для неизвестных заранее ключей.
Record<"email" | "phone", string>лучше, чем{ [key: string]: string }, потому что опечатку в ключе поймает компилятор; - сопоставленные типы:
{ [K in keyof T]: FieldConfig<T[K]> }строит конфигурацию по модели данных, а переименование ключей черезasвместе с шаблонным литеральным типом даёт производные имена видаonEmailChange; - вывод из конфига: массив полей объявляют с
as constи проверяют черезsatisfies FieldConfig[](с TypeScript 4.9). Тип значений формы получают из него, и конфиг остаётся единственным источником правды.
Отдельно скажите про рантайм: типы стираются при компиляции. Если конфигурация приходит с сервера, её надо проверить схемой, например Zod, и получить тип через z.infer.
В switch по kind добавьте ветку default с const never: never = field: новый вид поля без обработки станет ошибкой компиляции.
Где спотыкаются
Пишут value: any. Интерфейс формально есть, но компилятор уже ничего не проверяет, и ошибки уезжают в рантайм.
Ставят индексную сигнатуру там, где ключи известны. Опечатка в имени поля становится валидным кодом, а без noUncheckedIndexedAccess обращение к несуществующему ключу ещё и не даёт undefined в типе.
Считают, что тип защищает от кривого JSON с бэкенда. Без проверки схемой ответ приводится к интерфейсу через as и ломается уже в компоненте.