Оваа статија е спонзорирана од SurveyJS Има ментален модел што повеќето програмери на React го споделуваат без воопшто да разговараат за тоа гласно. Дека формите секогаш треба да бидат компоненти. Ова значи оџак како:
Формулар React Hook за локална состојба (минимално пререндерирање, ергономска регистрација на теренот, императивна интеракција). Зод за валидација (коректност на внесувањето, валидација на границите, парсирање безбедно за тип). Реагирајте го барањето за задниот дел: поднесување, повторни обиди, кеширање, синхронизација на серверот и така натаму.
И за огромното мнозинство форми - вашите екрани за најавување, вашите страници за поставки, вашите модали CRUD - ова функционира навистина добро. Секое парче си ја врши својата работа, тие чисто составуваат и можете да преминете на деловите од вашата апликација кои всушност го разликуваат вашиот производ. Но, одвреме-навреме, формуларот почнува да акумулира работи како правила за видливост кои зависат од претходните одговори или изведени вредности кои каскадираат низ три полиња. Можеби дури и цели страници што треба да се прескокнат или да се прикажат врз основа на вкупниот број. Првиот услов го ракувате со useWatch и вградена гранка, што е во ред. Потоа уште еден. Потоа, посегнувате до superRefine за да ги шифрира правилата на вкрстени полиња што вашата шема на Zod не може да ги изрази на нормален начин. Потоа, навигацијата со чекори почнува да протекува од деловната логика. Во одреден момент, гледате што сте изградиле и сфаќате дека формата веќе не е навистина интерфејс. Тоа е повеќе процес на одлучување, а стеблото на компонентите е токму онаму каде што случајно сте го складирале. Ова е местото каде што мислам дека менталниот модел за форми во React се распаѓа и навистина никој не е виновен. Стакот RHF + Zod е одличен во она за што е дизајниран. Прашањето е што ние тежнееме да продолжиме да го користиме надвор од точката каде што неговите апстракции се совпаѓаат со проблемот бидејќи алтернативата бара целосно различен начин на размислување за формите. Оваа статија е за таа алтернатива. За да го покажеме ова, двапати ќе ја изградиме истата форма со повеќе чекори:
Со React Hook Form + Zod поврзан со React Query за поднесување, Со SurveyJS, кој ја третира формата како податок - едноставна шема JSON - наместо како дрво со компоненти.
Исти барања, иста условна логика, ист API повик на крајот. Потоа ќе мапираме што точно се преселило и што останало, и ќе поставиме практичен начин да одлучите кој модел треба да го користите и кога. Формуларот што го градиме:
Оваа форма ќе користи проток од 4 чекори: Чекор 1: Детали
Име (задолжително), Е-пошта (задолжителен, валиден формат).
Чекор 2: Нарачајте
Единечна цена, Количина, Даночна стапка, Изведено: Субтот Данок, Вкупно.
Чекор 3: Сметка и повратни информации
Дали имате сметка? (Да/Не) Ако Да → корисничко име + лозинка, и двете се потребни. Ако не, → е-пошта веќе е собрана во чекор 1.
Оцена на задоволство (1–5) Ако ≥ 4 → прашате „Што ви се допадна? Ако ≤ 2 → прашате „Што можеме да подобриме?
Чекор 4: Преглед
Се појавува само ако вкупно >= 100 Конечно поднесување.
Ова не е екстремно. Но, доволно е да се разоткријат архитектонските разлики. Дел 1: Управувано од компоненти (React Hook Form + Zod) Инсталација npm инсталирај react-hook-form zod @hookform/resolvers @tanstack/react-query
Зод шема Да почнеме со Зод шемата, бидејќи обично таму се утврдува обликот на формата. За првите два чекори - лични податоци и внесување нарачки - сè е едноставно: потребни низи, броеви со минимум и број. Интересниот дел започнува кога се обидувате да ги изразите условните правила.
увоз { z } од „zod“;
извоз const formSchema = z.object({ firstName: z.string().min(1, "Задолжително"), email: z.string().email("Неважечка е-пошта"), цена: z.number().min(0), количина: z.number().min(1), даночна стапка: z.number(), has Account:(No"]Yes. z.string().optional(), лозинка: z.string().optional(), задоволство: z.number().min(1).max(5), positiveFeedback: z.string().optional(), подобрувањаПовратни информации: z.string().optional(),}).superRefine((податоци, {ifsda) =Yfcta"=>>> (!data.username) { ctx.addIssue({ код: "прилагодено", патека: ["корисничко име"], порака: "Задолжително" } } if (!data.password || data.password.length < 6) {ctx.addIssue({ code: "custom", знакот"}); }
if (data.satisfaction >= 4 && !data.positiveFeedback) { ctx.addIssue({ код: „прилагодено“, патека: [„позитивни повратни информации“], порака: „Ве молиме споделете што ви се допаѓа“ }); }
if (data.satisfaction <= 2 && !data.improvementFeedback) { ctx.addIssue({ код: „прилагодено“, патека:["improvementFeedback"], порака: "Ве молиме кажете ни што да подобриме" }); }});
тип на извоз FormData = z.infer
Забележете дека корисничкото име и лозинката се напишани како опционални() иако тие се условно потребни затоа што шемата на ниво на тип на Зод ја опишува формата на објектот, а не правилата кои регулираат кога полињата се важни. Условното барање мора да живее во суперРефине, кое работи откако формата е потврдена и има пристап до целосниот објект. Тоа разделување не е мана; Тоа е токму она за што е дизајнирана алатката: superRefine е местото каде што оди логиката меѓу полињата кога не може да се изрази во самата структура на шемата. Она што е исто така забележливо овде е она што оваа шема не го изразува. Нема концепт за страници, нема концепт за тоа кои полиња се видливи во која точка и нема концепт за навигација. Сето тоа ќе живее на друго место. Компонента на формуларот
увоз { useForm, useWatch } од "react-hook-form";увези { zodResolver } од "@hookform/resolvers/zod";увези { useMutation } од "@tanstack/react-query";увоз {useState, useMemo } од "react";увези тип {formSchema";
const ЧЕКОРИ = ["детали", "нарачка", "сметка", "преглед"];
тип OrderPayload = FormData & { subtotal: number; данок: број; вкупно: број };
функција за извоз RHFMultiStepForm() { const [чекор, setStep] = useState(0);
const мутација = useMutation({ mutationFn: асинхронизирано (оптоварување: OrderPayload) => { const res = чекај донеси("/api/orders", { метод: "POST", заглавија: { "Content-Type": "application/json" }, тело: JSON.stringify(payload), }); ако (!res.ok) фрли нова Грешка ("Не успеа да се достави"); врати рес.json(); }, });
const { register, control, handleSubmit, formState: { errors }, } = useForm
врати (
);}Видете го пенкалото SurveyJS-03-RHF [чаталено] од шесте исклучување. Многу работи се случуваат овде, и вреди да се забави за да забележите каде завршија работите.
Изведените вредности - субтотал, данок, вкупно - се пресметуваат во компонентата преку useWatch и useMemo бидејќи тие зависат од вредностите на полето во живо и нема друго природно место за нив. Правилата за видливост за корисничкото име, лозинката, позитивните повратни информации и подобрувањето на повратните информации се во живо во JSX како вградени услови. Логиката за прескокнување чекори - страницата за преглед се појавува само кога вкупно >= 100 - е вградена во променливата showSubmit и условот за рендерирање на чекор 3. Самата навигација е само бројач за користење држава што рачно го зголемуваме. React Query се справува со повторени обиди, кеширање и неважење. Формата само повикува mutation.mutate со потврдени податоци.
Ништо од ова не е погрешно, само по себе. Ова е сè уште идиоматски React, а компонентата е доста перформанси благодарение на тоа како RHF изолира повторно се прикажува. Но, ако треба да му го предадете ова на некој што не го напишал и да побарате од него да објасни под кои услови се појавува страницата за преглед, тој ќе мора да следи преку showSubmit, условот за прикажување на чекор 3 и логиката на копчето за навигација - три одделни места - за да го реконструира правилото што може да се наведе во една линија. Формата работи, да, но однесувањето навистина не може да се провери како систем. Тоа треба да се изврши ментално. Уште поважно, неговото менување бара инженерско вклучување. Дури и мала промена, како прилагодување кога ќе се појави чекорот за преглед, значи уредување на компонентата, ажурирање на валидацијата, отворање барање за повлекување, чекање преглед и повторно распоредување. Дел 2: Шема управувано (SurveyJS) Сега да го изградиме истиот проток користејќи шема. Инсталација npm инсталирај survey-core survey-react-ui @tanstack/react-query
Survey-core Мотор за траење, лиценциран од MIT, независен од платформа, кој го напојува прикажувањето на формите на SurveyJS - делот за кој се грижиме овде. Потребна е шема JSON, од неа се гради внатрешен модел и се справува со сето она што инаку би живеело во вашата компонента React: оценување на изразите за видливост, пресметување изведени вредности, управување со состојбата на страницата, следење валидација и одлучување што значи „целосно“ со оглед на тоа кои страници се всушност прикажани.
Survey-react-ui Слој за интерфејс / рендерирање што го поврзува тој модел со React. Во суштина, тоа е компонента
Заедно, тие ви даваат целосно функционално времетраење на формуларот на повеќе страници без да пишувате ниту една линија на контролен тек. Самиот формат на шемата е, како што беше кажано претходно, само JSON - без DSL или било што неслободно. Можете да го вметнете, да го увезете од датотека, да го преземете од API или да го складирате во колона со база на податоци и да го хидрирате при извршување. Истата форма, како и податоците Еве ја истата форма, овојпат изразена како JSON објект. Шемата дефинира сè: структура, валидација, правила за видливост, изведени пресметки, навигација на страница - и ја предава на Модел што ја оценува при извршување. Еве како изгледа тоа во целост:
извоз const surveySchema = { наслов: "Проток на нарачка", showProgressBar: "горе", страници: [ { име: "детали", елементи: [ { тип: "текст", име: "firstName", isRequired: true }, { тип: "текст", име: "email", inputType: "email", isRequired: "email": }] } ] }, { име: „нарачка“, елементи: [ { тип: „текст“, име: „цена“, влезен тип: „број“, стандардна вредност: 0 }, {тип: „текст“, име: „количина“, влез Тип: „број“, стандардна вредност: 1 }, {тип: „опаѓачки“,име: „даночна стапка“, стандардна вредност: 0,1, избори: [ { вредност: 0,05, текст: „5%“ }, {вредност: 0,1, текст: „10%“ }, {вредност: 0,15, текст: „15%“ } ] }, {тип: „израз“, име: „{}: {antprice}“; „израз“, име: „данок“, израз: „{субтотал} {taxRate}“ }, { тип: „израз“, име: „вкупно“, израз: „{подвкупно} + {данок}“ } ] }, {име: „сметка“, елементи: [ { тип: „радиогрупа“, име: „има: „има Сметка“, тип: „Име“, „Име: „Не "корисничко име", visualIf: "{hasAccount} = 'Да'", е Потребно: точно }, { тип: "текст", име: "лозинка", inputType: "лозинка", visualIf: "{hasAccount} = 'Да'", е Потребно: точно, валидатори: [{ тип: "текст", мин., 6. напише visualIf: "{total} >= 100", елементи: [] } ]};
Споредете го ова со верзијата RHF за момент.
Блокот superRefine кој условно бараше корисничко име и лозинка исчезна. visualIf: „{hasAccount} = „Да“ во комбинација со isRequired: true се справува со двете проблеми заедно, на самото поле, каде што би очекувале да ги најдете. Синџирот useWatch + useMemo што ги пресметува субтотал, данок и вкупен износ се заменува со три полиња за изразување кои меѓусебно се повикуваат по име. Состојбата на страницата за преглед, која во верзијата RHF можеше да се реконструира само со следење преку showSubmit, гранката за прикажување на чекор 3. И, конечно, логиката на копчето за навигација е единствена особина за видливо ако на објектот на страницата.
Таму е истата логика. Едноставно, шемата му дава место за живеење каде што е видливо изолирано, наместо да се шири низ компонентата. Исто така, имајте во предвид дека шемата користи тип: „израз“ за потвкуп, данок и вкупно. Изразот е само за читање и се користи главно за прикажување на пресметаните вредности. SurveyJS поддржува и тип: „html“ за статична содржина, но за пресметаните вредности, изразот е вистинскиот избор. Сега за страната на React. Рендерирање и поднесување Многу едноставно. Поврзете се наComplete на вашиот API на ист начин - преку useMutation или обична фаќање:
увоз { useState, useEffect, useRef } од „react“;увези { useMutation } од „@tanstack/react-query“;увези { Model } од „survey-core“;увези { Survey } од „survey-react-ui“;увези „survey-core/survey-query“.
функција за извоз SurveyForm() { const [модел] = useState(() => new Model(surveySchema));
const мутација = useMutation({ mutationFn: асинхронизиран (податоци) => { const res = чекај донеси("/api/orders", { метод: "POST", заглавија: { "Content-Type": "application/json" }, тело: JSON.stringify(податоци), }); ако (!res.ok) фрли нова Грешка ("Не успеа да се достави"); врати рес.json(); }, });
const mutationRef = useRef(мутација); mutationRef.current = мутација; useEffect(() => { const handler = (испраќач) => mutationRef.current.mutate(sender.data); model.onComplete.add(handler); return () => model.onComplete.remove(handler);}, [model]); // ref избегнува пререгистрација на управувачот секој рендер (промена на идентитетот на објектот на мутација)
врати се (
<>
Видете го Pen SurveyJS-03-SurveyJS [forked] by sixthextinction.
onComplete се вклучува кога корисникот ќе стигне до крајот на последната видлива страница. Значи, ако вкупниот број никогаш не премине 100 и страницата за преглед е прескокната, таа сепак се вклучува правилно бидејќи SurveyJS ја проценува видливоста пред да одлучи што значи „последна страница“. Потоа, sender.data ги содржи сите одговори заедно со пресметаните вредности (субтотал, данок, вкупно) како полиња од прва класа, така што товарот на API е идентичен со она што верзијата RHF рачно ја состави во onSubmit. НаmutationRef шаблонот е истиот шаблон по кој би посегнал секаде каде што ви треба стабилен управувач со настани над вредност што се менува на секој рендер - ништо специфично за SurveyJS за тоа.
Компонентата React повеќе не содржи никаква деловна логика. Нема useWatch, нема условен JSX, нема бројач на чекори, нема useMemo синџир, нема superRefine. React го прави она во што всушност е добар: рендерирање на компонента и поврзување со API повик. Што се пресели надвор од Реакт?
Загриженост RHF Стак SurveyJS Видливост филијали на JSX видливи Ако Изведени вредности употребиWatch / useMemo изразување Правила за вкрстени терени суперРафинирај Шема услови Навигација чекор држава Страницата видливаАко Локација на правилата Дистрибуирани низ датотеките Централизирано во шемата
Она што останува во React е распоредот, стилот, ожичувањето за поднесување и интеграцијата на апликациите, што ќе рече, работите за кои React е всушност дизајниран. Сè друго е преместено во шемата, и бидејќи шемата е само JSON објект, може да се складира во база на податоци, да се верзии независно од кодот на вашата апликација или да се уредува преку внатрешна алатка без да се бара распоредување. Управувачот со производи кој треба да го промени прагот што ја активира страницата за преглед може да го направи тоа без да ја допира компонентата. Тоа е значајна оперативна разлика за тимовите каде однесувањето на формата еволуира често и не е секогаш управувано од инженери. Кога да се користи секој пристап? Еве едно добро правило што функционира за мене: замислете целосно да ја избришете формата. Што би изгубиле?
Ако се работи за екрани, сакате форми управувани од компоненти. Ако е деловна логика, како што се прагови, правила за разгранување и условни барања што ги кодираат вистинските одлуки, сакате шема за мотор.
Слично на тоа, ако промените што доаѓаат главно се однесуваат на етикети, полиња и распоред, RHF ќе ви служи добро. Ако се работи за услови, резултати и правила што можеби ќе треба да ги приспособат вашите операциони служби или правен тим во вторник попладне без да поднесете билет, моделот на шема со SurveyJS е поискрен. Овие два пристапа навистина не се во конкуренција еден со друг. Тие се однесуваат на различни класи на проблеми, а грешката што вреди да се избегне е неусогласеноста на апстракцијата со тежината на логиката - третирање на системот на правила како компонента затоа што тоа е позната алатка или посегнување по мотор на политика затоа што формата порасна на три чекори и доби условно поле. Формата што ја изградивме овде се наоѓа близу до границата намерно, доволно сложена за да ја открие разликата, но не толку екстремна што споредбата се чувствува наместена. Повеќето реални форми кои станале неприкосновени во вашата база на кодови веројатно се наоѓаат во близина на истата граница, и обично прашањето е дали некој именувал што всушност се тие. Користете React Hook Form + Zod кога:
Формите се ориентирани кон CRUD; Логиката е плитка и управувана од UI; Инженерите го поседуваат целото однесување; Backend останува изворот на вистината.
Користете SurveyJS кога:
Обрасците ги кодираат деловните одлуки; Правилата се развиваат независно од UI; Логиката мора да биде видлива, да може да се ревидира или да биде верзиирана; Неинженерите влијаат на однесувањето; Истата форма мора да се протега низ повеќе страни.