@reformer/renderer-json
Форма целиком как JSON: операторы $model/$component/$dataSource + реестр компонентов.
@reformer/renderer-json описывает всю форму декларативной JSON-схемой (JsonFormSchema, M1). Привязки к модели, компонентам и источникам данных кодируются строками-операторами ($model(...), $component(...), $dataSource(...)), поэтому схема — чистый JSON: её можно положить в .json, принять строкой с сервера/CMS и отрисовать в разных UI-китах через реестр — без per-field React-кода.
Пакет строится поверх @reformer/renderer-react: JSON-схема конвертируется в RenderNode-дерево и рендерится тем же движком. Ключевая идея M1: модель (FormModel) — источник данных, JSON-схема — layout. Модель передаётся через провайдер, листья схемы (value: '$model(path)') биндятся к её сигналам.
Установка
npm install @reformer/renderer-json @reformer/core @reformer/renderer-react
@reformer/ui-kit — опциональный peer: используйте его (или свои компоненты) для наполнения реестра.
Быстрый пример
Схема — чистые данные, операторы-строки, никаких React-импортов:
{
"version": "1.0",
"root": {
"component": "$component(Box)",
"children": [
{
"value": "$model(email)",
"component": "$component(Input)",
"componentProps": { "label": "Email" }
}
]
}
}
Рендеринг: createJsonForm собирает форму из схемы за один проход (создаёт модель, конвертирует схему в дерево нод, строит форму) и возвращает бандл { model, form, schema, registry }; useJsonForm держит его стабильным между ре-рендерами. Реестр (defineRegistry) сопоставляет имена из схемы React-компонентам и отдаётся глобально через JsonRendererProvider; сам бандл передаётся рендереру пропом form:
import { Input, Box, FormField } from '@reformer/ui-kit';
import {
JsonFormRenderer,
JsonRendererProvider,
createJsonForm,
useJsonForm,
defineJsonSchema,
defineRegistry,
FIELD_WRAPPER,
} from '@reformer/renderer-json';
type MyForm = { email: string };
// defineJsonSchema<T> типизирует литерал по форме T: путь $model(...) сужается до Path<MyForm>
// (опечатка `$model(emial)` — ошибка компиляции), и не нужен `as unknown as JsonFormSchema`.
const schema = defineJsonSchema<MyForm>({
version: '1.0',
root: {
component: '$component(Box)',
children: [
{
value: '$model(email)',
component: '$component(Input)',
componentProps: { label: 'Email' },
},
],
},
});
const registry = defineRegistry((reg) => {
reg.component('Input', Input);
reg.component('Box', Box);
reg.component(FIELD_WRAPPER, FormField); // системная обёртка полей
});
export function MyFormPage() {
// Сборка одним проходом: createJsonForm создаёт модель из `initial`, конвертирует схему и строит
// форму. useJsonForm вызывает фабрику РОВНО один раз (ленивый useState), поэтому model/form
// переживают ре-рендеры (useMemo для этого не годится — React вправе сбросить его кэш).
const jsonForm = useJsonForm(() =>
createJsonForm<MyForm>({ schema, registry, initial: { email: '' } })
);
// Реестр — глобально через провайдер; бандл — пропом `form` (он несёт schema+model).
return (
<JsonRendererProvider settings={{ registry }}>
<JsonFormRenderer<MyForm> form={jsonForm} validateSchema={import.meta.env.DEV} />
</JsonRendererProvider>
);
}
JsonFormRenderer принимает { form?, schema?, model?, renderBehavior?, onSchemaReady?, validateSchema? }. Достаточно передать либо бандл form (из createJsonForm — он поставляет schema + model вместе), либо пару schema + model напрямую как альтернативу. Без того и другого рендерер бросит provide a `form` bundle (createJsonForm) or both `schema` and `model` props.
Альтернатива без бандла. Если модель уже создана отдельно (
createModel) или нужен низкоуровневый контроль, передавайтеschemaиmodelразными пропами:<JsonFormRenderer schema={schema} model={model} />.createJsonFormумеет принять готовую модель пропомmodelвместоinitial, а декларативное поведение — пропомbehavior.
Что внутри
- createJsonForm / useJsonForm — сборка формы из схемы одним проходом.
createJsonForm<T>({ schema, registry, initial | model, behavior? })возвращает бандл{ model, form, schema, registry };useJsonForm(factory)кэширует его стабильно (ленивыйuseState). Бандл целиком отдаётся рендереру пропомform— схема передаётся один раз, вместо прежней двойной передачи (convertJsonToM1TreeдляcreateForm+ отдельный пропschema). - defineJsonSchema<T> — идентити-хелпер, типизирующий литерал схемы по форме
T: пути внутри$model(...)сужаются доPath<T>(опечатка ловится компилятором), не нуженas unknown as JsonFormSchema. Для схемы-строки-с-сервера (тип неизвестен) —JsonFormSchemaбез параметра. Пути внутриitem.$templateотносительны элементу и не типизируются. - JsonFormRenderer — главный компонент-рендерер; получает бандл
form(или паруschema+model) пропами, реестр — из контекста провайдера.JsonRendererProviderзадаёт глобальные настройки{ registry }, аuseJsonRendererSettingsчитает текущие настройки. - Операторы — строки
$model(path)(привязка листа к сигналу модели),$component(Name)(компонент из реестра),$dataSource(Name)(значение-источник). Разбираются черезparseOperator/isModelOp/isComponentOp/isDataSourceOp; голые строки идут как есть. - Реестр —
defineRegistryстроит карту имён на компоненты и dataSource-значения.FIELD_WRAPPER('$fieldWrapper') — зарезервированный ключ для компонента-обёртки полей (label, error, hint), обычноFormFieldиз@reformer/ui-kit. - Валидация схемы — мета-схема form-DSL (
formSchemaMetaSchema,buildFormSchemaMetaSchema,getComponentNames,getDataSourceNames, ajv-free). ПолныйvalidateFormSchemaживёт в отдельной точке входа@reformer/renderer-json/validate(тянет ajv, не попадает в render-бандл);JsonFormRendererгрузит её динамически приvalidate={true}, ошибки рисуетSchemaErrorPanel.
Для низкоуровневого доступа доступны convertJsonToM1Tree (JSON → RenderNode-дерево для createForm({ model, schema })) и createRenderSchemaFromJsonM1 (JSON → RenderSchemaFn), а также type guards isFieldNode / isArrayNode / isContainerNode. Обычно эти функции вызывать не нужно: createJsonForm инкапсулирует convertJsonToM1Tree + createForm, а JsonFormRenderer — createRenderSchemaFromJsonM1. Тяните их напрямую только для ручной сборки.