Tento článok je sponzorovaný spoločnosťou SurveyJS Existuje mentálny model, ktorý väčšina vývojárov Reactu zdieľa bez toho, aby o tom nahlas diskutovali. Že formuláre sú vždy súčasťou. To znamená zásobník ako:
Formulár React Hook pre miestny štát (minimálne opätovné vykreslenie, ergonomická registrácia v teréne, nevyhnutná interakcia). Zod na overenie (správnosť vstupu, overenie hraníc, typovo bezpečná analýza). React Query pre backend: odoslanie, opakované pokusy, ukladanie do vyrovnávacej pamäte, synchronizácia servera atď.
A pre veľkú väčšinu formulárov – vaše prihlasovacie obrazovky, stránky s nastaveniami, vaše modály CRUD – to funguje naozaj dobre. Každý kus robí svoju prácu, skladá sa čisto a môžete prejsť k častiam vašej aplikácie, ktoré skutočne odlišujú váš produkt. Raz za čas však formulár začne hromadiť veci, ako sú pravidlá viditeľnosti, ktoré závisia od skorších odpovedí, alebo odvodené hodnoty, ktoré kaskádovito prechádzajú cez tri polia. Možno aj celé stránky, ktoré by sa mali preskočiť alebo zobraziť na základe priebežného súčtu. Prvú podmienku zvládnete pomocou useWatch a inline vetvy, čo je v poriadku. Potom ďalší. Potom siahate po superRefine, aby ste zakódovali pravidlá medzi poľami, ktoré vaša schéma Zod nedokáže vyjadriť normálnym spôsobom. Potom začne kroková navigácia presakovať obchodnú logiku. V určitom okamihu sa pozriete na to, čo ste vytvorili, a uvedomíte si, že formulár už v skutočnosti nie je používateľským rozhraním. Je to skôr rozhodovací proces a strom komponentov je presne tam, kde ste ho náhodou uložili. Tu si myslím, že mentálny model pre formy v Reacte sa rozpadá a v skutočnosti za to nemôže nikto. Stoh RHF + Zod je vynikajúci v tom, na čo bol navrhnutý. Problém je v tom, že máme tendenciu ho používať až za bod, kde jeho abstrakcie zodpovedajú problému, pretože alternatíva vyžaduje úplne iný spôsob myslenia o formách. Tento článok je o tejto alternatíve. Aby sme to ukázali, vytvoríme presne ten istý viackrokový formulár dvakrát:
S React Hook Form + Zod pripojeným k React Query na odoslanie, S SurveyJS, ktorý zaobchádza s formulárom ako s údajmi – jednoduchou schémou JSON – a nie so stromom komponentov.
Rovnaké požiadavky, rovnaká podmienená logika, rovnaké volanie API na konci. Potom presne zmapujeme, čo sa presťahovalo a čo zostalo, a navrhneme praktický spôsob, ako rozhodnúť, ktorý model by ste mali použiť a kedy. Formulár, ktorý vytvárame:
Tento formulár bude používať 4-krokový postup: Krok 1: Podrobnosti
Krstné meno (povinné), E-mail (povinné, platný formát).
Krok 2: Objednávka
jednotková cena, množstvo, sadzba dane, odvodené: medzisúčet, daň, Celkom.
Krok 3: Účet a spätná väzba
Máte účet? (Áno/Nie) Ak Áno → používateľské meno + heslo, musia sa obidva údaje. Ak Nie → e-mail už bol zhromaždený v kroku 1.
Hodnotenie spokojnosti (1 – 5) Ak ≥ 4 → opýtajte sa „Čo sa vám páčilo?“ Ak ≤ 2 → opýtajte sa „Čo môžeme zlepšiť?“
Krok 4: Kontrola
Zobrazí sa iba vtedy, ak je celkový počet >= 100 Záverečné podanie.
To nie je extrém. Ale na odhalenie architektonických rozdielov to stačí. Časť 1: Poháňané komponentmi (React Hook Form + Zod) Inštalácia npm install response-hook-form zod @hookform/resolvers @tanstack/react-query
Schéma Zod Začnime so schémou Zod, pretože to je zvyčajne miesto, kde sa vytvorí tvar formulára. V prvých dvoch krokoch – osobné údaje a zadanie objednávky – je všetko jednoduché: požadované reťazce, čísla s minimami a enum. Zaujímavá časť začína, keď sa pokúsite vyjadriť podmienené pravidlá.
import { z } z "zod";
export const formSchema = z.object({ meno: z.string().min(1, "Povinné"), e-mail: z.string().email("Neplatný e-mail"), cena: z.číslo().min(0), množstvo: z.číslo().min(1), sadzba dane: z.číslo(), hasAccount: z.enum: z.meno používateľa(["] voliteľné), z.string). z.string().voliteľné(), spokojnosť: z.číslo().min(1).max(5), pozitívna spätná väzba: z.string().voliteľné(), zlepšenieSpätná väzba: z.string().voliteľné(),}).superUpresniť((údaje, ctx) => { if (data.hasAccount === "Áno") { ife (!custom{data.dx:meno používateľa. cesta: ["username"], správa: "Povinné" } } if (!data.password || data.password.length < 6) { ctx.addIssue({ kód: "vlastné", cesta: ["heslo"], správa: "Minimálne 6 znakov" } });
if (data.satisfaction >= 4 && !data.positiveFeedback) { ctx.addIssue({ kód: "custom", cesta: ["positiveFeedback"], správa: "Zdieľajte, čo sa vám páčilo" }); }
if (data.satisfaction <= 2 && !data.improvementFeedback) { ctx.addIssue({ kód: "vlastné", cesta:["improvementFeedback"], správa: "Povedzte nám, čo máme zlepšiť" }); }});
typ exportu FormData = z.infer
Všimnite si, že používateľské meno a heslo sú zadané ako voliteľné (), aj keď sú podmienene povinné, pretože Zodova schéma na úrovni typu popisuje tvar objektu, nie pravidlá, ktorými sa riadi, keď na poliach záleží. Podmienená požiadavka musí existovať vo vnútri superRefine, ktorá sa spustí po overení tvaru a má prístup k celému objektu. Toto oddelenie nie je chybou; je to presne to, na čo je tento nástroj určený: superRefine je miesto, kde ide logika medzi poľami, keď ju nemožno vyjadriť v samotnej štruktúre schémy. Pozoruhodné je aj to, čo táto schéma nevyjadruje. Nemá žiadnu koncepciu stránok, žiadnu koncepciu toho, ktoré polia sú v ktorom bode viditeľné, a žiadnu koncepciu navigácie. To všetko bude žiť niekde inde. Komponent formulára
import { useForm, useWatch } z "react-hook-form";import { zodResolver } z "@hookform/resolvers/zod";import { useMutation } z "@tanstack/react-query";import { useState, useMemo } z "react";import" {formDatachema};
const STEPS = ["podrobnosti", "objednávka", "účet", "recenzia"];
zadajte OrderPayload = FormData & { medzisúčet: číslo; daň: číslo; celkove: cislo };
exportova funkcia RHFMultiStepForm() { const [krok, setStep] = useState(0);
const mutácia = useMutation({ mutationFn: async (užitočné zaťaženie: OrderPayload) => { const res = wait fetch("/api/orders", { metóda: "POST", hlavičky: { "Content-Type": "application/json" }, telo: JSON.stringify(úžitkové zaťaženie), }); if (!res.ok) hodiť novú chybu("Nepodarilo sa odoslať"); return res.json(); }, });
const { register, control, handleSubmit, formState: { errors }, } = useForm
return (
);}Pozrite si Pen SurveyJS-03-RHF [rozdvojený] podľa sixthextinction. Deje sa tu toho dosť a stojí za to spomaliť, aby ste si všimli, kde veci skončili.
Odvodené hodnoty – medzisúčet, daň, súčet – sú vypočítané v komponente cez useWatch a useMemo, pretože závisia od aktuálnych hodnôt poľa a neexistuje pre ne žiadne iné prirodzené miesto. Pravidlá viditeľnosti pre používateľské meno, heslo, positiveFeedback a ImproveFeedback fungujú v JSX ako vložené podmienky. Logika preskakovania krokov – stránka s recenziou sa zobrazí len vtedy, keď je celkový počet >= 100 – je vložená do premennej showSubmit a podmienky vykreslenia v kroku 3. Samotná navigácia je len počítadlo useState, ktoré manuálne zvyšujeme. React Query spracováva opakované pokusy, ukladanie do vyrovnávacej pamäte a zrušenie platnosti. Formulár iba volá mutation.mutate s overenými údajmi.
Nič z toho nie je samo o sebe zlé. Toto je stále idiomatický React a komponent je celkom výkonný vďaka tomu, ako RHF izoluje re-rendery. Ak by ste to však odovzdali niekomu, kto to nenapísal, a požiadali by ste ho, aby vysvetlil, za akých podmienok sa stránka s recenziou zobrazuje, musel by vysledovať cez showSubmit, podmienku vykreslenia kroku 3 a logiku navigačného tlačidla – tri samostatné miesta – aby zrekonštruoval pravidlo, ktoré by mohlo byť uvedené v jednom riadku. Formulár funguje, áno, ale správanie nie je v skutočnosti kontrolovateľné ako systém. Musí to byť vykonané mentálne. Ešte dôležitejšie je, že jeho zmena si vyžaduje zapojenie inžinierov. Dokonca aj malé vylepšenie, ako je úprava, keď sa zobrazí krok kontroly, znamená úpravu komponentu, aktualizáciu overenia, otvorenie požiadavky na stiahnutie, čakanie na kontrolu a opätovné nasadenie. Časť 2: Riadené schémou (SurveyJS) Teraz vytvoríme rovnaký tok pomocou schémy. Inštalácia npm install survey-core survey-react-ui @tanstack/react-query
survey-core Modul runtime nezávislý na platforme s licenciou MIT, ktorý poháňa vykresľovanie formulárov SurveyJS – časť, na ktorej nám záleží. Berie schému JSON, vytvára z nej interný model a spracováva všetko, čo by inak fungovalo vo vašom komponente React: vyhodnocovanie výrazov viditeľnosti, výpočet odvodených hodnôt, spravovanie stavu stránky, sledovanie overenia a rozhodovanie o tom, čo znamená „úplné“ vzhľadom na to, ktoré stránky sa skutočne zobrazili.
survey-react-ui UI / renderovacia vrstva, ktorá spája tento model s Reactom. Je to v podstate komponent
Spoločne vám poskytujú plne funkčný, viacstránkový modul runtime bez písania jediného riadku toku kontroly. Samotný formát schémy je, ako už bolo povedané, iba JSON – žiadne DSL ani nič proprietárne. Môžete ho vložiť, importovať zo súboru, načítať z API alebo uložiť do stĺpca databázy a hydratovať ho za behu. Rovnaký formulár ako údaje Tu je rovnaký formulár, tentoraz vyjadrený ako objekt JSON. Schéma definuje všetko: štruktúru, validáciu, pravidlá viditeľnosti, odvodené výpočty, navigáciu po stránkach – a odovzdá to modelu, ktorý to vyhodnotí za behu. Takto to vyzerá v plnom znení:
export const surveySchema = { title: "Tok objednávky", showProgressBar: "top", pages: [ { name: "details", elements: [ { type: "text", name: "firstName", isRequired: true }, { type: "text", name: "email", inputType: "email", isRequired: isRequired: true" "e-mail", validators:]} ] }, { name: "order", elements: [ { type: "text", name: "price", inputType: "number", defaultValue: 0 }, { type: "text", name: "quantity", inputType: "number", defaultValue: 1 }, { type: "dropdown",name: "taxRate", defaultValue: 0,1, options: [ { value: 0,05, text: "5%" }, { value: 0,1, text: "10%" }, { value: 0,15, text: "15%" } ] }, { type: "expression", name: "{type:"quantity", expression, "{ typeprice}", expression,} názov: "daň", výraz: "{medzisúčet} {daňová sadzba}" }, { typ: "výraz", názov: "celkom", výraz: "{medzisúčet} + {daň}" } ] }, { názov: "účet", prvky: [ { typ: "rádiová skupina", názov: "hasAccount", možnosti: ["Áno", "Nie"] }, názov: "Ak počet: "text" 'Áno'", isRequired: true }, { type: "text", name: "password", inputType: "password", visibleIf: "{hasAccount} = 'Yes'", isRequired: true, validators: [{ type: "text", minLength: 6, text: "Min 6 characters" }] }, { name: ", rating Minssfaction: " rateMax: 5 }, { type: "comment", name: "positiveFeedback", visibleIf: "{satisfaction} >= 4" }, { type: "comment", name: "improvementFeedback", visibleIf: "{satisfaction} <= 2" } ] }, { name: "{satisfaction} <= 2" } ] }, { name: "{satisfaction" 1 viditelný >=: "]}
Porovnajte to na chvíľu s verziou RHF.
Blok superRefine, ktorý podmienečne vyžadoval používateľské meno a heslo, je preč. ViditeľnéIf: "{hasAccount} = 'Áno'" v kombinácii s isRequired: true rieši oba problémy spoločne, na samotnom poli, kde by ste ich očakávali. Reťazec useWatch + useMemo, ktorý vypočítal medzisúčet, daň a súčet, je nahradený tromi výrazovými poľami, ktoré sa navzájom odkazujú podľa názvu. Stav stránky s recenziou, ktorý bol vo verzii RHF rekonštruovateľný iba sledovaním cez showSubmit, krok 3 render vetvy. A nakoniec, logika tlačidla navigácie je jediná vlastnosť viditeľných If na objekte stránky.
Je tam rovnaká logika. Ide len o to, že schéma mu dáva miesto na bývanie, kde je viditeľná izolovane, namiesto toho, aby sa šírila po komponente. Všimnite si tiež, že schéma používa typ: 'výraz' pre medzisúčet, daň a súčet. Výraz je len na čítanie a používa sa hlavne na zobrazenie vypočítaných hodnôt. SurveyJS tiež podporuje typ: 'html' pre statický obsah, ale pre vypočítané hodnoty je výraz správnou voľbou. Teraz na stranu React. Vykresľovanie a odoslanie Veľmi jednoduché. Zapojte onComplete do svojho API rovnakým spôsobom – cez useMutation alebo plain fetch:
import { useState, useEffect, useRef } z "react";import { useMutation } z "@tanstack/react-query";import { Model } z "survey-core";import { Survey } z "survey-react-ui";import "survey-core/survey-core.css";
export funkcie SurveyForm() { const [model] = useState(() => new Model(surveySchema));
const mutácia = useMutation({ mutationFn: async (údaje) => { const res = wait fetch("/api/orders", { metóda: "POST", hlavičky: { "Content-Type": "application/json" }, telo: JSON.stringify(údaje), }); if (!res.ok) hodiť novú chybu("Nepodarilo sa odoslať"); return res.json(); }, });
const mutationRef = useRef(mutácia); mutáciaRef.prúd = mutácia; useEffect(() => { const handler = (odosielateľ) => mutationRef.current.mutate(odosielateľ.údaje); model.onComplete.add(handler); return () => model.onComplete.remove(handler); }, [model]); // ref zabráni opätovnej registrácii obslužného programu pri každom vykreslení (zmeny identity objektu mutácie)
vrátiť (
<>
Pozrite si Pen SurveyJS-03-SurveyJS [rozdvojený] podľa sixthextinction.
onComplete sa spustí, keď používateľ dosiahne koniec poslednej viditeľnej stránky. Takže ak súčet nikdy neprekročí 100 a stránka s recenziou sa preskočí, stále sa spustí správne, pretože SurveyJS vyhodnotí viditeľnosť pred rozhodnutím, čo znamená „posledná stránka“. Sender.data potom obsahuje všetky odpovede spolu s vypočítanými hodnotami (medzisúčet, daň, súčet) ako prvotriedne polia, takže užitočné zaťaženie API je identické s tým, čo verzia RHF zostavila manuálne v onSubmit. TheVzor mutationRef je rovnaký, po ktorom by ste siahli kdekoľvek, kde potrebujete stabilnú obsluhu udalosti nad hodnotou, ktorá sa mení pri každom vykreslení – nič špecifické pre SurveyJS.
Komponent React už neobsahuje žiadnu obchodnú logiku. Neexistujú žiadne useWatch, žiadne podmienené JSX, žiadne počítadlo krokov, žiadne useMemo chain, žiadne superRefine. React robí to, v čom je skutočne dobrý: vykresľuje komponent a pripája ho k volaniu API. Čo sa posunulo mimo reakcie?
Obavy RHF zásobník SurveyJS Viditeľnosť JSX pobočky viditeľnéAk Odvodené hodnoty používaťSledovať / používaťPoznámku výraz Medzipoľné pravidlá superSpresniť Podmienky schémy Navigácia krokový stav Strana viditeľnáAk Umiestnenie pravidla Distribuované medzi súbormi Centralizované v schéme
To, čo v Reacte zostáva, je rozloženie, štýl, zapojenie odosielania a integrácia aplikácií, teda veci, na ktoré je React skutočne navrhnutý. Všetko ostatné sa presunulo do schémy, a keďže schéma je len objektom JSON, môže byť uložená v databáze, verzovaná nezávisle od kódu vašej aplikácie alebo upravovaná pomocou interných nástrojov bez potreby nasadenia. Produktový manažér, ktorý potrebuje zmeniť prah, ktorý spúšťa stránku recenzie, to môže urobiť bez toho, aby sa dotkol komponentu. To je zmysluplný prevádzkový rozdiel pre tímy, kde sa správanie formulárov často vyvíja a nie vždy ho riadia inžinieri. Kedy použiť jednotlivé prístupy? Tu je dobré pravidlo, ktoré pre mňa funguje: predstavte si úplné odstránenie formulára. Čo by ste stratili?
Ak ide o obrazovky, chcete formuláre riadené komponentmi. Ak je to obchodná logika, ako sú prahy, pravidlá vetvenia a podmienené požiadavky, ktoré kódujú skutočné rozhodnutia, chcete nástroj schém.
Podobne, ak sa prichádzajúce zmeny týkajú najmä štítkov, polí a rozloženia, RHF vám poslúži dobre. Ak sa týkajú podmienok, výsledkov a pravidiel, ktoré váš operačný alebo právny tím bude možno musieť upraviť v utorok popoludní bez podania lístka, model schémy s SurveyJS je úprimnejší. Tieto dva prístupy si v skutočnosti navzájom nekonkurujú. Zaoberajú sa rôznymi triedami problémov a chybou, ktorej sa treba vyhnúť, je nesúlad abstrakcie s váhou logiky – zaobchádzanie so systémom pravidiel ako s komponentom, pretože je to známy nástroj, alebo po siahnutí po nástroji politiky, pretože formulár sa rozrástol na tri kroky a získal podmienené pole. Forma, ktorú sme tu vytvorili, sa nachádza blízko hranice zámerne, je dostatočne zložitá na to, aby odhalila rozdiel, ale nie taká extrémna, aby sa porovnanie javilo ako zmanipulované. Väčšina skutočných foriem, ktoré sa vo vašej kódovej základni stali nepraktickými, sa pravdepodobne nachádza blízko tej istej hranice a otázkou zvyčajne je, či niekto pomenoval, čo vlastne sú. Použite React Hook Form + Zod, keď:
Formuláre sú orientované na CRUD; Logika je plytká a riadená používateľským rozhraním; Inžinieri vlastnia všetko správanie; Backend zostáva zdrojom pravdy.
SurveyJS použite, keď:
Formuláre kódujú obchodné rozhodnutia; Pravidlá sa vyvíjajú nezávisle od používateľského rozhrania; Logika musí byť viditeľná, auditovateľná alebo verzovaná; Neinžinieri ovplyvňujú správanie; Rovnaký formulár musí bežať na viacerých frontendoch.