Ta članek sponzorira SurveyJS Obstaja miselni model, ki ga deli večina React razvijalcev, ne da bi o njem kdaj razpravljali na glas. Da naj bi bili obrazci vedno sestavni deli. To pomeni sklad, kot je:
Obrazec React Hook za lokalno stanje (minimalne ponovne upodobitve, ergonomska registracija polja, nujna interakcija). Zod za preverjanje veljavnosti (pravilnost vnosa, preverjanje meje, varno razčlenjevanje). Poizvedba React za zaledje: predložitev, ponovni poskusi, predpomnjenje, sinhronizacija strežnika itd.
In za veliko večino obrazcev – vaše prijavne zaslone, vaše nastavitvene strani, vaše CRUD modale – to deluje zelo dobro. Vsak kos opravlja svoje delo, sestavijo se čisto in lahko nadaljujete z deli vaše aplikacije, ki dejansko razlikujejo vaš izdelek. Toda obrazec vsake toliko časa začne kopičiti stvari, kot so pravila vidnosti, ki so odvisna od prejšnjih odgovorov, ali izpeljane vrednosti, ki se vrstijo skozi tri polja. Morda celo celotne strani, ki bi jih bilo treba preskočiti ali prikazati glede na tekočo vsoto. Prvi pogojnik obravnavate z useWatch in inline vejo, kar je v redu. Potem še eno. Potem posežete po superRefine za kodiranje pravil med polji, ki jih vaša shema Zod ne more izraziti na običajen način. Nato navigacija po korakih začne uhajati iz poslovne logike. Na neki točki pogledate, kaj ste zgradili, in ugotovite, da obrazec v resnici ni več uporabniški vmesnik. To je bolj postopek odločanja in drevo komponent je točno tam, kjer ste ga shranili. Tukaj mislim, da se miselni model za obrazce v Reactu pokvari in za to res ni nihče kriv. Sklad RHF + Zod je odličen v tem, za kar je bil zasnovan. Težava je v tem, da jo še naprej uporabljamo čez točko, ko se njene abstrakcije ujemajo s problemom, ker alternativa zahteva povsem drugačen način razmišljanja o oblikah. Ta članek govori o tej alternativi. Da bi to pokazali, bomo dvakrat zgradili popolnoma enak večstopenjski obrazec:
Z React Hook Form + Zod je povezan z React Query za predložitev, S SurveyJS, ki obrazec obravnava kot podatke – preprosto shemo JSON – in ne kot drevo komponent.
Iste zahteve, ista pogojna logika, isti klic API-ja na koncu. Nato bomo natanko preslikali, kaj se je premaknilo in kaj ostalo, ter predstavili praktičen način za odločitev, kateri model bi morali uporabiti in kdaj. Obrazec, ki ga gradimo:
Ta obrazec bo uporabljal potek v 4 korakih: 1. korak: podrobnosti
Ime (obvezno), E-pošta (obvezno, veljavna oblika).
2. korak: Naročilo
cena na enoto, količina, davčna stopnja, Izpeljano: Vmesni seštevek, davek, Skupaj.
3. korak: račun in povratne informacije
Ali imate račun? (da/ne) Če je odgovor Da → uporabniško ime + geslo, potrebno je oboje. Če ne → e-pošta je že zbrana v 1. koraku.
Ocena zadovoljstva (1–5) Če je ≥ 4 → vprašajte "Kaj vam je bilo všeč?" Če je ≤ 2 → vprašajte "Kaj lahko izboljšamo?"
4. korak: Pregled
Prikaže se le, če je skupno >= 100 Končna oddaja.
To ni ekstrem. Toda dovolj je, da izpostavimo arhitekturne razlike. 1. del: na podlagi komponent (React Hook Form + Zod) Namestitev npm namestite obliko react-hook zod @hookform/resolvers @tanstack/react-query
Zod shema Začnimo s shemo Zod, ker se tam običajno vzpostavi oblika obrazca. Za prva dva koraka – osebne podatke in vnose naročil – je vse preprosto: zahtevani nizi, števila z minimumi in enum. Zanimiv del se začne, ko poskušate izraziti pogojna pravila.
uvoz { z } iz "zod";
export const formSchema = z.object({ firstName: z.string().min(1, "Required"), email: z.string().email("Invalid email"), price: z.number().min(0), quantity: z.number().min(1), taxRate: z.number(), hasAccount: z.enum(["Yes", "No"]), username: z.string().optional(), geslo: z.string().optional(), zadovoljstvo: z.number().min(1).max(5), positiveFeedback: z.string().optional(), izboljšanjeFeedback: z.string().optional(),}).superRefine((data, ctx) => { if (data.hasAccount === "Yes") { if (!data.username) { ctx.addIssue({ code: "custom", path: ["username"], message: "Required" } } if (!data.password || data.password.length < 6) { ctx.addIssue({ code: "custom", path: ["password"], message: "Min 6 characters" });
if (data.satisfaction >= 4 && !data.positiveFeedback) { ctx.addIssue({ code: "custom", path: ["positiveFeedback"], message: "Prosimo, delite, kar vam je bilo všeč" }); }
if (data.satisfaction <= 2 && !data.improvementFeedback) { ctx.addIssue({ koda: "po meri", pot:["improvementFeedback"], sporočilo: "Prosimo, povejte nam, kaj naj izboljšamo" }); }});
izvozni tip FormData = z.infer
Upoštevajte, da sta uporabniško ime in geslo vnesena kot optional(), čeprav sta pogojno obvezna, ker Zodova shema na ravni tipa opisuje obliko predmeta in ne pravil, ki urejajo, kdaj so polja pomembna. Pogojna zahteva mora živeti znotraj superRefine, ki se zažene po potrditvi oblike in ima dostop do celotnega objekta. Ta ločitev ni napaka; to je tisto, za kar je orodje zasnovano: superRefine je tisto, kamor gre logika navzkrižnih polj, ko je ni mogoče izraziti v sami strukturi sheme. Tukaj je opazno tudi to, česar ta shema ne izraža. Nima koncepta strani, pojma o tem, katera polja so vidna na kateri točki, in koncepta navigacije. Vse to bo živelo nekje drugje. Komponenta obrazca
uvoz { useForm, useWatch } iz "react-hook-form"; uvoz { zodResolver } iz "@hookform/resolvers/zod"; uvoz { useMutation } iz "@tanstack/react-query"; uvoz { useState, useMemo } iz "react"; uvoz { formSchema, tip FormData } iz "./schema";
const STEPS = ["podrobnosti", "naročilo", "račun", "pregled"];
type OrderPayload = FormData & { subtotal: number; davek: številka; skupaj: število };
izvozna funkcija RHFMultiStepForm() { const [korak, setStep] = useState(0);
mutacija const = useMutation({ mutationFn: async (payload: OrderPayload) => { const res = await fetch("/api/orders", { metoda: "POST", headers: { "Content-Type": "application/json" }, telo: JSON.stringify(payload), }); if (!res.ok) throw new Error("Fail to submit"); vrni res.json(); }, });
const { register, control, handleSubmit, formState: { errors }, } = useForm
return (
);}Oglejte si Pen SurveyJS-03-RHF [razcepljeno] s strani sixthextinction. Tukaj se dogaja kar veliko in vredno je upočasniti, da opazimo, kje so se stvari končale.
Izpeljane vrednosti – vmesni seštevek, davek, skupni znesek – se izračunajo v komponenti prek useWatch in useMemo, ker so odvisne od vrednosti polja v živo in zanje ni drugega naravnega mesta. Pravila vidnosti za uporabniško ime, geslo, pozitivne povratne informacije in povratne informacije o izboljšanju živijo v JSX kot vgrajeni pogojniki. Logika preskakovanja korakov – stran za pregled se prikaže le, če je skupno >= 100 – je vdelana v spremenljivko showSubmit in pogoj upodabljanja v 3. koraku. Sama navigacija je le števec useState, ki ga ročno povečujemo. React Query obravnava ponovne poskuse, predpomnjenje in razveljavitev. Obrazec samo pokliče mutation.mutate s potrjenimi podatki.
Nič od tega samo po sebi ni narobe. To je še vedno idiomatski React in komponenta je precej zmogljiva zahvaljujoč temu, kako RHF izolira ponovno upodabljanje. Če pa bi to predali nekomu, ki tega ni napisal, in ga prosili, naj pojasni, pod kakšnimi pogoji se prikaže stran za pregled, bi moral slediti skozi showSubmit, pogoj upodabljanja 3. koraka in logiko gumba za krmarjenje – tri ločena mesta –, da bi rekonstruiral pravilo, ki bi lahko bilo navedeno v eni vrstici. Oblika deluje, da, vendar vedenje kot sistem v resnici ni mogoče pregledati. Izvesti ga je treba mentalno. Še pomembneje, spreminjanje zahteva inženirsko sodelovanje. Tudi majhna prilagoditev, kot je prilagoditev, ko se prikaže korak pregleda, pomeni urejanje komponente, posodobitev preverjanja, odpiranje zahteve za vlečenje, čakanje na pregled in ponovno uvajanje. 2. del: Na podlagi sheme (SurveyJS) Sedaj pa zgradimo isti tok z uporabo sheme. Namestitev npm namestite survey-core survey-react-ui @tanstack/react-query
survey-coreOd platforme neodvisen izvajalni mehanizem z licenco MIT, ki poganja upodabljanje obrazcev SurveyJS – del, ki nas tukaj zanima. Vzame shemo JSON, iz nje zgradi notranji model in obravnava vse, kar bi sicer živelo v vaši komponenti React: ocenjevanje izrazov vidnosti, računanje izpeljanih vrednosti, upravljanje stanja strani, sledenje validaciji in odločanje, kaj pomeni »popolno« glede na to, katere strani so bile dejansko prikazane.
survey-react-uiUporabniški vmesnik/plast upodabljanja, ki povezuje ta model z Reactom. To je v bistvu komponenta
Skupaj vam nudita popolnoma funkcionalno večstransko izvajalno okolje brez pisanja ene same vrstice nadzornega toka. Sama oblika sheme je, kot je bilo že rečeno, samo JSON - brez DSL ali česar koli lastniškega. Lahko ga vgradite, uvozite iz datoteke, pridobite iz API-ja ali shranite v stolpec zbirke podatkov in ga hidrirate med izvajanjem. Isti obrazec kot podatki Tukaj je ista oblika, tokrat izražena kot objekt JSON. Shema definira vse: strukturo, validacijo, pravila vidnosti, izpeljane izračune, navigacijo po straneh — in jo preda modelu, ki jo oceni med izvajanjem. Tako je videti v celoti:
export const surveySchema = { title: "Order Flow", showProgressBar: "top", pages: [ { name: "details", elements: [ { type: "text", name: "firstName", isRequired: true }, { type: "text", name: "email", inputType: "email", isRequired: true, validatorji: [{ type: "email", text: "Invalid email" }] } ] }, { name: "naročilo", elementi: [ { type: "text", name: "price", inputType: "number", defaultValue: 0 }, { type: "text", name: "quantity", inputType: "number", defaultValue: 1 }, { type: "dropdown",ime: "taxRate", defaultValue: 0.1, izbire: [ { value: 0.05, text: "5%" }, { value: 0.1, text: "10%" }, { value: 0.15, text: "15%" } ] }, { type: "expression", name: "subtotal", izraz: "{price} {quantity}" }, { type: "expression", ime: "davek", izraz: "{subtotal} {taxRate}" }, { type: "expression", name: "total", izraz: "{subtotal} + {tax}" } ] }, { name: "account", elementi: [ { type: "radiogroup", name: "hasAccount", možnosti: ["Yes", "No"] }, { type: "text", name: "uporabniško ime", visibleIf: "{hasAccount} = 'Da'", isRequired: true }, { type: "text", name: "password", inputType: "password", visibleIf: "{hasAccount} = 'Yes'", isRequired: true, validatorji: [{ type: "text", minLength: 6, text: "Min 6 characters" }] }, { type: "ocena", name: "zadovoljstvo", rateMin: 1, rateMax: 5 }, { type: "comment", name: "positiveFeedback", visibleIf: "{satisfaction} >= 4" }, { type: "comment", name: "improvementFeedback", visibleIf: "{satisfaction} <= 2" } ] }, { name: "review", visibleIf: "{total} >= 100", elementi: [] } ]};
Za trenutek primerjajte to z različico RHF.
Blok superRefine, ki je pogojno zahteval uporabniško ime in geslo, ni več. visibleIf: "{hasAccount} = 'Da'" v kombinaciji z isRequired: true obravnava obe zadevi skupaj, na samem polju, kjer bi ju pričakovali. Veriga useWatch + useMemo, ki je izračunala delno vsoto, davek in skupno, je nadomeščena s tremi izraznimi polji, ki se med seboj sklicujejo po imenu. Pogoj strani za pregled, ki ga je bilo v različici RHF mogoče rekonstruirati le s sledenjem skozi showSubmit, vejo upodabljanja 3. koraka. In končno, logika gumba za krmarjenje je ena lastnost visibleIf na predmetu strani.
Tam je ista logika. Samo shema mu daje prostor za življenje, kjer je viden ločeno, namesto da bi bil razpršen po komponenti. Upoštevajte tudi, da shema uporablja type: 'expression' za delno vsoto, davek in skupno. Izraz je samo za branje in se uporablja predvsem za prikaz izračunanih vrednosti. SurveyJS podpira tudi vrsto: 'html' za statično vsebino, vendar je za izračunane vrednosti izraz prava izbira. Zdaj pa stran React. Upodabljanje in predložitev Zelo preprosto. Povežite onComplete z vašim API-jem na enak način – prek useMutation ali navadnega pridobivanja:
uvoz { useState, useEffect, useRef } iz "react"; uvoz { useMutation } iz "@tanstack/react-query"; uvoz { Model } iz "survey-core"; uvoz { ankete } iz "survey-react-ui"; uvoz "survey-core/survey-core.css";
izvozna funkcija SurveyForm() { const [model] = useState(() => new Model(surveySchema));
mutacija const = useMutation({ mutationFn: async (podatki) => { const res = await fetch("/api/orders", { metoda: "POST", headers: { "Content-Type": "application/json" }, telo: JSON.stringify(podatki), }); if (!res.ok) throw new Error("Fail to submit"); vrni res.json(); }, });
const mutationRef = useRef(mutacija); mutationRef.current = mutacija; useEffect(() => { const handler = (sender) => mutationRef.current.mutate(sender.data); model.onComplete.add(handler); return () => model.onComplete.remove(handler); }, [model]); // ref se izogne ponovni registraciji upravljalnika pri vsakem upodabljanju (sprememba identitete predmeta mutacije)
vrnitev (
<>
Oglejte si Pen SurveyJS-03-SurveyJS [razcepljen] s strani sixthextinction.
onComplete se sproži, ko uporabnik doseže konec zadnje vidne strani. Torej, če vsota nikoli ne preseže 100 in je stran za pregled preskočena, se še vedno sproži pravilno, ker SurveyJS oceni vidnost, preden se odloči, kaj pomeni »zadnja stran«. Nato sender.data vsebuje vse odgovore skupaj z izračunanimi vrednostmi (vmesni seštevek, davek, skupno) kot prvorazredna polja, tako da je koristna obremenitev API-ja enaka tisti, ki jo je različica RHF sestavila ročno v onSubmit. TheVzorec mutationRef je isti, po katerem bi posegli povsod, kjer potrebujete stabilnega obdelovalca dogodkov nad vrednostjo, ki se spremeni pri vsakem upodabljanju – pri njem ni nič posebnega za SurveyJS.
Komponenta React ne vsebuje več nobene poslovne logike. Ni useWatch, ni pogojnega JSX, ni števca korakov, ni verige useMemo, ni superRefine. React počne tisto, v čemer je pravzaprav dober: upodablja komponento in jo povezuje s klicem API-ja. Kaj se je premaknilo iz Reacta?
Zaskrbljenost RHF sklad SurveyJS Vidnost veje JSX visibleIf Izpeljane vrednosti useWatch / useMemo izražanje Pravila med tereni superRefine Pogoji sheme Navigacija stanje koraka Stran vidnaIf Lokacija pravila Porazdeljeno po datotekah Centralizirano v shemi
Kar ostane v Reactu, je postavitev, slog, ožičenje oddaje in integracija aplikacij, kar pomeni, za kar je React dejansko zasnovan. Vse ostalo se je premaknilo v shemo in ker je shema samo predmet JSON, jo je mogoče shraniti v zbirko podatkov, različico neodvisno od vaše kode aplikacije ali urejati z notranjim orodjem, ne da bi zahtevala uvedbo. Produktni vodja, ki mora spremeniti prag, ki sproži stran za pregled, lahko to stori, ne da bi se dotaknil komponente. To je pomembna operativna razlika za ekipe, kjer se vedenje oblike pogosto razvija in ga ne vodijo vedno inženirji. Kdaj uporabiti posamezen pristop? Tukaj je dobro pravilo, ki deluje zame: predstavljajte si, da v celoti izbrišete obrazec. Kaj bi izgubil?
Če gre za zaslone, želite oblike, ki jih poganjajo komponente. Če je poslovna logika, kot so pragovi, pravila razvejanja in pogojne zahteve, ki kodirajo resnične odločitve, potrebujete mehanizem sheme.
Podobno, če se spremembe nanašajo predvsem na oznake, polja in postavitev, vam bo RHF dobro služil. Če se nanašajo na pogoje, rezultate in pravila, ki jih bo vaša operativna ali pravna ekipa morda morala prilagoditi v torek popoldne, ne da bi vložili kazen, je model sheme s SurveyJS bolj pošten. Ta dva pristopa nista v resnici konkurenčna drug drugemu. Obravnavajo različne razrede problemov in napaka, ki se ji je vredno izogniti, je neusklajevanje abstrakcije s težo logike – obravnavanje sistema pravil kot komponente, ker je to znano orodje, ali poseganje po mehanizmu pravilnika, ker je obrazec zrasel na tri korake in pridobil pogojno polje. Oblika, ki smo jo zgradili tukaj, namerno leži blizu meje, dovolj zapletena, da izpostavi razliko, vendar ne tako ekstremna, da bi se primerjava zdela prirejena. Večina resničnih oblik, ki so v vaši kodni bazi postale okorne, verjetno leži blizu te iste meje in vprašanje je običajno le, ali je kdo poimenoval, kaj dejansko so. Uporabite React Hook Form + Zod, ko:
Obrazci so usmerjeni v CRUD; Logika je plitka in temelji na uporabniškem vmesniku; Inženirji so lastniki vsega vedenja; Backend ostaja vir resnice.
Uporabite SurveyJS, ko:
Obrazci kodirajo poslovne odločitve; Pravila se razvijajo neodvisno od uporabniškega vmesnika; Logika mora biti vidna, revidirana ali verzirana; Neinženirji vplivajo na vedenje; Isti obrazec se mora izvajati na več frontendih.