Seda artiklit sponsoreerib SurveyJS On olemas vaimne mudel, mida enamik Reacti arendajaid jagab, ilma et oleks seda kunagi valjusti arutanud. Et vormid peaksid alati olema komponendid. See tähendab virna nagu:
React Hook Form kohaliku oleku jaoks (minimaalne ümberrenderdus, ergonoomiline välja registreerimine, kohustuslik suhtlus). Zod valideerimiseks (sisestuse õigsus, piiride valideerimine, tüübikindel sõelumine). Reageerimispäring taustaprogrammi jaoks: esitamine, korduskatsed, vahemällu salvestamine, serveri sünkroonimine ja nii edasi.
Ja enamiku vormide puhul – sisselogimisekraanid, seadete lehed, CRUD-modaalid – töötab see väga hästi. Iga tükk teeb oma tööd, koostab puhtalt ja saate liikuda rakenduse nende osade juurde, mis teie toodet tegelikult eristavad. Kuid aeg-ajalt hakkab vorm koguma asju, nagu nähtavuse reeglid, mis sõltuvad varasematest vastustest, või tuletatud väärtused, mis liiguvad läbi kolme välja. Võib-olla isegi terveid lehti, mis tuleks jooksva kogusumma põhjal vahele jätta või näidata. Esimest tingimust käsitlete useWatchi ja tekstisisese haruga, mis on hea. Siis teine. Seejärel kasutate superRefine'i, et kodeerida väljadevahelisi reegleid, mida teie Zodi skeem ei suuda tavapärasel viisil väljendada. Seejärel hakkab astmeline navigeerimine äriloogikat lekkima. Mingil hetkel vaatate, mida olete ehitanud, ja mõistate, et vorm pole enam kasutajaliides. See on rohkem otsustusprotsess ja komponentide puu on just seal, kus te selle salvestasite. See on koht, kus ma arvan, et Reacti vormide vaimne mudel laguneb ja see pole tegelikult kellegi süü. RHF + Zod pinu on selle jaoks suurepärane, milleks see oli mõeldud. Probleem on selles, et me kipume seda kasutama kohast, kus selle abstraktsioonid vastavad probleemile, sest alternatiiv nõuab vormidest täiesti erinevat mõtlemisviisi. See artikkel räägib sellest alternatiivist. Selle näitamiseks koostame täpselt sama mitmeastmelise vormi kaks korda:
Kui React Hook Form + Zod on edastamiseks ühendatud React Queryga, SurveyJS-iga, mis käsitleb vormi pigem andmetena – lihtsa JSON-skeemina – kui komponendipuuna.
Samad nõuded, sama tingimuslik loogika, sama API kutse lõpus. Seejärel kaardistame täpselt, mis liikus ja mis jäi, ning koostame praktilise viisi, kuidas otsustada, millist mudelit ja millal kasutada. Vorm, mida me ehitame:
See vorm kasutab 4-astmelist voogu: 1. samm: üksikasjad
Eesnimi (nõutav), E-post (nõutav, kehtiv vorming).
2. samm: tellige
Ühiku hind, Kogus, maksumäär, Tuletatud: Vahesumma, maks, Kokku.
3. samm: konto ja tagasiside
Kas teil on konto? (jah/ei) Kui Jah → kasutajanimi + parool, on mõlemad nõutavad. Kui ei → e-kiri on juba 1. sammus kogutud.
Rahuloluhinnang (1–5) Kui ≥ 4 → küsi "Mis teile meeldis?" Kui ≤ 2 → küsi "Mida saame parandada?"
4. samm: vaadake üle
Kuvatakse ainult siis, kui kokku >= 100 Lõplik esitamine.
See ei ole äärmuslik. Kuid arhitektuuriliste erinevuste paljastamisest piisab. 1. osa: komponendipõhine (reageerimiskonksu vorm + Zod) Paigaldamine npm install react-hook-form zod @hookform/resolvers @tanstack/react-query
Zodi skeem Alustame Zodi skeemiga, sest tavaliselt kujuneb seal vormi kuju. Esimese kahe sammu puhul – isikuandmed ja tellimuse sisestused – on kõik lihtne: nõutavad stringid, numbrid miinimumiga ja loend. Huvitav osa algab siis, kui proovite väljendada tingimuslikke reegleid.
import { z } "zodist";
export const formSchema = z.object({eesnimi: z.string().min(1, "Nõutav"), email: z.string().email("Vigane e-posti aadress"), hind: z.number().min(0), kogus: z.number().min(1), taxRate: z.number(), hasAccount: ["],enum",("].esenum" z.string().optional(), parool: z.string().optional(), rahulolu: z.number().min(1).max(5), positiivneTagasiside: z.string().optional(), parendusTagasiside: z.string().optional(),}).superRefine((andmed, ctxdata) =.=.ha { if (ctxda) =. (!andmed.kasutajanimi) { ctx.addIssue({ kood: "kohandatud", tee: ["kasutajanimi"], sõnum: "nõutav" }); } if (!data.password || data.password.length < 6) { ctx.addIssue({ kood: "kohandatud sõna" , tee:]); } }
if (data.satisfaction >= 4 && !data.positiveFeedback) { ctx.addIssue({ kood: "custom", path: ["positiveFeedback"], sõnum: "Palun jagage, mis teile meeldis" }); }
if (data.satisfaction <= 2 && !data.improvementFeedback) { ctx.addIssue({ kood: "kohandatud", tee:["improvementFeedback"], teade: "Palun öelge meile, mida parandada" }); }});
ekspordi tüüp FormData = z.infer
Pange tähele, et kasutajanimi ja parool sisestatakse valikuna () ehkki need on tingimuslikult nõutavad, kuna Zodi tüübitaseme skeem kirjeldab objekti kuju, mitte väljade tähtsust reguleerivaid reegleid. Tingimuslik nõue peab asuma sees superRefine, mis käivitatakse pärast kuju kinnitamist ja millel on juurdepääs kogu objektile. See eraldamine ei ole viga; see on just see, milleks tööriist on loodud: superRefine on koht, kus väljadeülene loogika läheb siis, kui seda ei saa väljendada skeemi struktuuris endas. Siin on tähelepanuväärne ka see, mida see skeem ei väljenda. Sellel pole lehtede kontseptsiooni, kontseptsiooni selle kohta, millised väljad millisel hetkel on näha, ega ka navigeerimise kontseptsiooni. Kõik see elab kusagil mujal. Vormi komponent
import { useForm, useWatch } from "react-hook-form";import { zodResolver } from "@hookform/resolvers/zod";import { useMutation } from "@tanstack/react-query";import { useState, useMemo } kohast "react";import failist "Data/schema";
const STEPS = ["detailid", "tellimus", "konto", "ülevaade"];
tüüp TellimusPayload = FormData & { vahesumma: number; maks: number; kokku: arv };
ekspordi funktsioon RHFMultiStepForm() { const [samm, setStep] = useState(0);
const mutatsioon = useMutation({ mutationFn: async (kasutav koormus: OrderPayload) => { const res = oota fetch("/api/orders", { meetod: "POST", päised: { "Content-Type": "application/json" }, keha: JSON.stringify(payload), }); if (!res.ok) throw new Error("Ebaõnnestus esitada"); tagasta res.json(); }, });
const { register, control, handleSubmit, formState: { errors }, } = useForm
return (
);}Vaadake pliiatsiküsitlustJS-03-RHF [kahveldatud] kuue väljasurumise võrra. Siin toimub üsna palju ja tasub aeglustada, et märgata, kuhu asjad lõppesid.
Tuletatud väärtused – vahesumma, maks, kogusumma – arvutatakse komponendis funktsioonide useWatch ja useMemo kaudu, kuna need sõltuvad reaalajas väljade väärtustest ja nende jaoks pole muud loomulikku kohta. Kasutajanime, parooli, positiivse tagasiside ja täiustamise nähtavuse reeglid avaldatakse JSX-is sisemiste tingimustingimustena. Sammude vahelejätmise loogika – ülevaateleht kuvatakse ainult siis, kui koguarv >= 100 – on manustatud muutujasse showSubmit ja 3. sammu renderdustingimusse. Navigeerimine ise on lihtsalt UseState loendur, mida me käsitsi suurendame. React Query tegeleb korduskatsete, vahemällu salvestamise ja kehtetuks tunnistamisega. Vorm kutsub lihtsalt mutation.mutate valideeritud andmetega.
Ükski neist pole iseenesest vale. See on endiselt idiomaatiline React ja komponent on üsna tõhus tänu sellele, kuidas RHF isoleerib uuesti renderdamised. Kui aga annaksite selle kellelegi, kes pole seda kirjutanud, ja paluksite tal selgitada, millistel tingimustel arvustuse leht ilmub, peaksid nad jälgima läbi showSubmit, 3. sammu renderdustingimuse ja navigeerimisnupu loogika (kolm erinevat kohta), et rekonstrueerida reegel, mille oleks võinud esitada ühel real. Vorm töötab jah, kuid käitumine pole süsteemina tegelikult kontrollitav. Seda tuleb vaimselt teostada. Veelgi olulisem on see, et selle muutmine nõuab inseneri kaasamist. Isegi väike näpunäide, nagu ülevaatusetapi ilmumisel kohandamine, tähendab komponendi redigeerimist, valideerimise värskendamist, tõmbamistaotluse avamist, ülevaatuse ootamist ja uuesti juurutamist. 2. osa: skeemipõhine (SurveyJS) Nüüd loome sama voo skeemi abil. Paigaldamine npm install survey-core survey-react-ui @tanstack/react-query
survey-core MIT-i litsentsiga platvormist sõltumatu käitusmootor, mis toetab SurveyJS-i vormide renderdamist – see osa, millest me siin hoolime. See võtab JSON-skeemi, loob sellest sisemudeli ja haldab kõike, mis muidu teie Reacti komponendis elaks: nähtavuse avaldiste hindamine, tuletatud väärtuste arvutamine, lehe oleku haldamine, valideerimise jälgimine ja otsustamine, mida tähendab „täielik” arvestades, milliseid lehti tegelikult kuvati.
survey-react-uiKasutajaliides / renderduskiht, mis ühendab selle mudeli Reactiga. See on sisuliselt komponent
Üheskoos annavad need teile täielikult funktsionaalse mitmelehelise vormi käitusaja, ilma et peaksite kirjutama ühtki juhtvoo rida. Skeemivorming ise on, nagu varem öeldud, lihtsalt JSON - ei DSL-i ega midagi patenteeritud. Saate selle lisada, importida failist, tuua selle API-st või salvestada andmebaasi veergu ja hüdraatida seda käitusajal. Sama vorm, nagu andmed Siin on sama vorm, seekord väljendatud JSON-objektina. Skeem määratleb kõik: struktuuri, valideerimise, nähtavuse reeglid, tuletatud arvutused, lehel navigeerimise – ja annab selle mudelile, mis hindab seda käitusajal. Siin on, kuidas see tervikuna välja näeb:
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, in email, isRequired: true, in email:val], ] }, { nimi: "tellimus", elemendid: [ { type: "text", name: "price", inputType: "number", defaultValue: 0 }, { type: "text", name: "quantity", inputType: "number", defaultValue: 1 }, { type: "rippmenüü",nimi: "taxRate", defaultValue: 0,1, valikud: [ { väärtus: 0,05, tekst: "5%" }, { väärtus: 0,1, tekst: "10%" }, { väärtus: 0,15, tekst: "15%" } ] }, { tüüp: "ekspressioon", nimi: "osasumma, tüüp"}, avaldis: "nt{price}", "ekspressioon", nimi: "maks", avaldis: "{subtotal} {taxRate}" }, { type: "expression", nimi: "kokku", avaldis: "{subtotal} + {tax}" } ] }, { nimi: "konto", elemendid: [ { tüüp: "raadiorühm", nimi: "hasAccount", nimi: "hasAccount", tüüp ", {:"Y valikud: ", [":] "kasutajanimi", nähtavIf: "{hasAccount} = 'Jah'", isRequired: true }, { type: "text", name: "password", inputType: "password", nähtavIf: "{hasAccount} = 'Yes'", isRequired: true, validators: [{ type: "text ", min6 , text}th:"} tüüp: "hinnang", nimi: "rahulolu", rateMin: 1, rateMax: 5 }, { tüüp: "kommentaar", nimi: "positiivne tagasiside", nähtavKui: "{rahulolu} >= 4" }, { tüüp: "kommentaar", nimi: "täiustusTagasiside", nähtavIf: "{}nimi ], vaade"}", nähtavIf: "{kokku} >= 100", elemendid: [] } ]};
Võrrelge seda korraks RHF-versiooniga.
SuperRefine'i plokk, mis nõudis tingimuslikult kasutajanime ja parooli, on kadunud. nähtavIf: "{hasAccount} = 'Jah'" koos parameetriga isRequired: true käsitleb mõlemat probleemi koos, väljal endal, kust võiksite need leida. Ahel useWatch + useMemo, mis arvutas vahesumma, maksu ja summa, asendatakse kolme avaldiseväljaga, mis viitavad üksteisele nime järgi. Ülevaate lehe tingimus, mis RHF-i versioonis oli rekonstrueeritav ainult läbi showSubmit, toimingu 3 renderdusharu, jälgides. Ja lõpuks, navigeerimisnupu loogika on leheobjektil üks atribuut nähtaval.
Seal on sama loogika. See on lihtsalt see, et skeem annab sellele elukoha, kus see on nähtav eraldiseisvana, mitte üle komponendi laiali. Samuti pange tähele, et skeem kasutab vahesumma, maksu ja kogusumma jaoks tüüpi: "expression". Avaldis on kirjutuskaitstud ja seda kasutatakse peamiselt arvutatud väärtuste kuvamiseks. SurveyJS toetab ka staatilise sisu jaoks tüüpi: 'html', kuid arvutatud väärtuste puhul on avaldis õige valik. Nüüd Reacti poole kohta. Renderdamine ja esitamine Väga lihtne. Ühendage onComplete oma API-ga samal viisil – useMutationi või lihtsa toomise kaudu:
import { useState, useEffect, useRef } from "react";import { useMutation } from "@tanstack/react-query";import { Model } from "survey-core";import { Survey } from "survey-react-ui";import "survey-core/survey-core".
ekspordi funktsioon SurveyForm() { const [mudel] = useState(() => new Model(surveySchema));
const mutatsioon = useMutation({ mutatsioonFn: async (andmed) => { const res = oota fetch("/api/orders", { meetod: "POST", päised: { "Content-Type": "application/json" }, keha: JSON.stringify(data), }); if (!res.ok) throw new Error("Ebaõnnestus esitada"); tagasta res.json(); }, });
const mutatsioonRef = useRef(mutatsioon); mutationRef.current = mutatsioon; useEffect(() => { const handler = (saatja) => mutatsioonRef.current.mutate(sender.data); model.onComplete.add(handler); return () => model.onComplete.remove(handler); }, [mudel]); // ref väldib käitleja uuesti registreerimist iga renderduse korral (mutatsiooniobjekti identiteet muutub)
tagasi (
<>
Vaadake pliiatsiuuringutJS-03-SurveyJS [kahveldatud] kuue väljasurumise võrra.
onComplete käivitub, kui kasutaja jõuab viimase nähtava lehe lõppu. Nii et kui kogusumma ei ületa kunagi 100 ja ülevaateleht jäetakse vahele, käivitub see ikkagi õigesti, sest SurveyJS hindab nähtavust enne, kui otsustab, mida tähendab viimane leht. Seejärel sisaldab sender.data kõiki vastuseid koos arvutatud väärtustega (vahesumma, maks, kogusumma) esimese klassi väljadena, nii et API kasulik koormus on identne sellega, mida RHF-versioon onsubmitis käsitsi kokku pani. ThemutationRef muster on seesama, milleni jõuaksite kõikjal, kus vajate stabiilset sündmuste käitlejat iga renderduse korral muutuva väärtuse asemel – selles pole midagi SurveyJS-i spetsiifilist.
Komponent React ei sisalda enam üldse äriloogikat. Puudub useWatch, tingimuslik JSX, sammuloendur, useMemo kett ega superRefine. React teeb seda, milles ta tegelikult hea on: renderdab komponendi ja ühendab selle API-kõnega. Mis reageeris?
Mure RHF virn UuringJS Nähtavus JSX filiaalid nähtavKui Tuletatud väärtused useWatch / useMemo väljendus Ristväljade reeglid superrefine Skeemi tingimused Navigeerimine sammu olek Lehekülg nähtavKui Reegli asukoht Jaotatud failide vahel Tsentraliseeritud skeemis
Reactisse jääb paigutus, stiil, esitamise juhtmestik ja rakenduste integreerimine, st asjad, mille jaoks React on tegelikult loodud. Kõik muu teisaldati skeemi ja kuna skeem on lihtsalt JSON-objekt, saab selle salvestada andmebaasi, versioonida teie rakenduse koodist sõltumatult või redigeerida sisemiste tööriistade abil ilma juurutamist nõudmata. Tootehaldur, kes peab muutma ülevaatuse lehe käivitavat läve, saab seda teha ilma komponenti puudutamata. See on oluline töö erinevus meeskondade jaoks, kus vormikäitumine muutub sageli ja seda ei juhi alati insenerid. Millal iga lähenemisviisi kasutada? Siin on hea rusikareegel, mis minu jaoks töötab: kujutage ette, et kustutate vormi täielikult. Mida sa kaotaksid?
Kui tegemist on ekraanidega, soovite komponendipõhiseid vorme. Kui see on äriloogika, nagu läved, hargnemisreeglid ja tingimuslikud nõuded, mis kodeerivad tegelikke otsuseid, soovite skeemimootorit.
Samamoodi, kui teie teele tulevad muudatused puudutavad peamiselt silte, välju ja paigutust, on RHF teile kasulik. Kui need puudutavad tingimusi, tulemusi ja reegleid, mida teie operatsioonil või juriidilisel meeskonnal võib olla vaja teisipäeva pärastlõunal ilma piletit esitamata kohandada, sobib SurveyJS-iga skeemimudel ausamalt. Need kaks lähenemisviisi ei konkureeri tegelikult üksteisega. Need käsitlevad eri klasside probleeme ja vältimist vääriv viga on abstraktsiooni ja loogika kaalu mitteühitamine – reeglisüsteemi käsitlemine komponendina, sest see on tuttav tööriist, või poliitikamootori poole pöördumine, kuna vorm kasvas kolmeastmeliseks ja omandas tingimusliku välja. Vorm, mille me siin ehitasime, asub teadlikult piiri lähedal, piisavalt keeruline, et erinevust paljastada, kuid mitte nii äärmuslik, et võrdlus tundub võlts. Enamik tõelisi vorme, mis on teie koodibaasis kohmakaks muutunud, asuvad tõenäoliselt selle sama piiri lähedal ja tavaliselt on küsimus selles, kas keegi on nimetanud, mis nad tegelikult on. Kasutage React Hook Form + Zod, kui:
Vormid on CRUD-le orienteeritud; Loogika on pinnapealne ja kasutajaliidese juhitud; Insenerid omavad kogu käitumist; Taustaprogramm jääb tõe allikaks.
Kasutage SurveyJS-i, kui:
Vormid kodeerivad äriotsuseid; Reeglid arenevad kasutajaliidest sõltumatult; Loogika peab olema nähtav, auditeeritav või versioonidega; Mitteinsenerid mõjutavad käitumist; Sama vorm peab töötama mitmes kasutajaliideses.