Hierdie artikel word geborg deur SurveyJS Daar is 'n verstandelike model wat die meeste React-ontwikkelaars deel sonder om dit ooit hardop te bespreek. Dat vorms altyd veronderstel is om komponente te wees. Dit beteken 'n stapel soos:
Reageer-haakvorm vir plaaslike staat (minimale herlewerings, ergonomiese veldregistrasie, noodsaaklike interaksie). Zod vir validering (invoer korrektheid, grens validering, tipe-veilige ontleding). Reageer navraag vir backend: indiening, herproberings, cache, bedienersinkronisering, ensovoorts.
En vir die oorgrote meerderheid vorms - jou aanmeldskerms, jou instellingsbladsye, jou CRUD-modale - werk dit baie goed. Elke stuk doen sy werk, hulle komponeer skoon, en jy kan aanbeweeg na die dele van jou toepassing wat jou produk eintlik onderskei. Maar kort-kort begin 'n vorm dinge ophoop soos sigbaarheidsreëls wat afhang van vroeëre antwoorde, of afgeleide waardes wat deur drie velde val. Miskien selfs hele bladsye wat op grond van 'n lopende totaal oorgeslaan of gewys moet word. Jy hanteer die eerste voorwaardelike met 'n useWatch en 'n inlyn-tak, wat goed is. Dan nog een. Dan gryp jy na superRefine om kruisveldreëls te enkodeer wat jou Zod-skema nie op die normale manier kan uitdruk nie. Dan begin stapnavigasie besigheidslogika uitlek. Op 'n stadium kyk jy na wat jy gebou het en besef dat die vorm nie regtig meer UI is nie. Dit is meer 'n besluitproses, en die komponentboom is net waar jy dit toevallig gestoor het. Dit is waar ek dink die verstandelike model vir vorms in React breek, en dit is regtig niemand se skuld nie. Die RHF + Zod-stapel is uitstekend waarvoor dit ontwerp is. Die probleem is dat ons geneig is om dit aan te hou gebruik verby die punt waar die abstraksies daarvan ooreenstem met die probleem, want die alternatief vereis 'n heeltemal ander manier van dink oor vorms. Hierdie artikel handel oor daardie alternatief. Om dit te wys, sal ons presies dieselfde multi-stap vorm twee keer bou:
Met React Hook Form + Zod bedraad na React Query vir indiening, Met SurveyJS, wat 'n vorm as data behandel - 'n eenvoudige JSON-skema - eerder as 'n komponentboom.
Dieselfde vereistes, dieselfde voorwaardelike logika, dieselfde API-oproep aan die einde. Dan sal ons presies karteer wat beweeg en wat gebly het, en 'n praktiese manier uiteensit om te besluit watter model jy moet gebruik, en wanneer. Die vorm wat ons bou:
Hierdie vorm sal 'n 4-stap vloei gebruik: Stap 1: Besonderhede
Voornaam (vereis), E-pos (vereis, geldige formaat).
Stap 2: Bestel
Eenheidsprys, Hoeveelheid, Belastingkoers, Afgelei: Subtotaal, Belasting, Totaal.
Stap 3: Rekening en terugvoer
Het jy 'n rekening? (Ja/Nee) Indien Ja → gebruikersnaam + wagwoord, beide vereis. Indien Nee → e-pos reeds in stap 1 ingesamel.
Tevredenheidgradering (1–5) As ≥ 4 → vra “Waarvan het jy gehou?” As ≤ 2 → vra “Wat kan ons verbeter?”
Stap 4: Hersien
Verskyn slegs as totaal >= 100 Finale voorlegging.
Dit is nie ekstreem nie. Maar dit is genoeg om argitektoniese verskille bloot te lê. Deel 1: Komponentgedrewe (Reageerhaakvorm + Zod) Installasie npm installeer react-hook-form zod @hookform/resolvers @tanstack/react-query
Zod Skema Kom ons begin met die Zod-skema, want dit is gewoonlik waar die vorm van die vorm vasgestel word. Vir die eerste twee stappe - persoonlike besonderhede en bestelinvoere - is alles eenvoudig: vereiste stringe, nommers met minimums en 'n enum. Die interessante deel begin wanneer jy probeer om die voorwaardelike reëls uit te druk.
invoer { z } vanaf "zod";
uitvoer const formSkema = z.object({ voornaam: z.string().min(1, "Vereis"), e-pos: z.string().email("Ongeldige e-pos"), prys: z.number().min(0), hoeveelheid: z.number().min(1), belastingtarief: z.number(), hetRekening: z.enum(No"),(natuurlik), gebruikernaam([".Yes), op gebruikernaam([".Yes)." wagwoord: z.string().opsioneel(), bevrediging: z.number().min(1).maks(5), positieweTerugvoer: z.string().opsioneel(), verbeteringTerugvoer: z.string().opsioneel(),}).superRefine((data, ctx) => { if (data.hasRekening ===) "Ja" (!) ctx.addIssue({ kode: "pasgemaak", pad: ["gebruikersnaam"], boodskap: "Vereis" } } if (!data.wagwoord || data.wagwoord.length < 6) { ctx.addIssue({ kode: "pasmaak", pad: ["wagwoord"], boodskap: "Min 6 karakters"});
if (data.satisfaction >= 4 && !data.positiveFeedback) { ctx.addIssue({ kode: "custom", pad: ["positieweTerugvoer"], boodskap: "Deel asseblief waarvan jy gehou het" }); }
if (data.satisfaction <= 2 && !data.improvementTerugvoer) { ctx.addIssue({ kode: "custom", pad:["verbeteringTerugvoer"], boodskap: "Vertel ons asseblief wat om te verbeter" }); }});
uitvoer tipe FormData = z.infer
Let daarop dat gebruikersnaam en wagwoord as opsioneel () getik word, alhoewel dit voorwaardelik vereis word omdat Zod se tipe-vlak-skema die vorm van die voorwerp beskryf, nie die reëls wat bepaal wanneer velde saak maak nie. Die voorwaardelike vereiste moet binne superRefine leef, wat loop nadat die vorm bekragtig is en toegang tot die volle voorwerp het. Daardie skeiding is nie 'n gebrek nie; dit is net waarvoor die instrument ontwerp is: superRefine is waar kruisveldlogika gaan wanneer dit nie in die skemastruktuur self uitgedruk kan word nie. Wat ook hier opvallend is, is wat hierdie skema nie uitdruk nie. Dit het geen konsep van bladsye nie, geen konsep van watter velde op watter punt sigbaar is nie, en geen konsep van navigasie nie. Dit alles sal iewers anders woon. Vormkomponent
invoer { useForm, useWatch } vanaf "react-hook-form"; voer { zodResolver } van "@hookform/resolvers/zod" in; voer { useMutation } in vanaf "@tanstack/react-query"; voer { useState, useMemo } in vanaf "react"; voer {form.schema in" in, tik vorm./skema };
const STEPS = ["besonderhede", "bestelling", "rekening", "resensie"];
tipe OrderPayload = FormData & { subtotaal: getal; belasting: nommer; totaal: getal };
uitvoerfunksie RHFMultiStepForm() { const [stap, setStep] = useState(0);
const mutation = useMutation({ mutationFn: async (payload: OrderPayload) => { const res = wag op haal("/api/orders", { metode: "POS", headers: { "Content-Type": "application/json" }, liggaam: JSON.stringify(loonvrag), }); if (!res.ok) gooi nuwe Fout ("Kon nie indien nie"); gee res.json(); }, });
const { register, control, handleSubmit, formState: { errors }, } = useForm
gee terug (
);}Sien die Pen SurveyJS-03-RHF [gevurk] deur sixthextinction. Hier gebeur nogal baie, en dit is die moeite werd om stadiger te gaan om te sien waar dinge beland het.
Die afgeleide waardes - subtotaal, belasting, totaal - word in die komponent via useWatch en useMemo bereken omdat dit afhanklik is van lewendige veldwaardes en daar is geen ander natuurlike plek vir hulle nie. Die sigbaarheidsreëls vir gebruikernaam, wagwoord, positiewe terugvoer en verbetering is terugvoer in JSX as inlyn voorwaardes. Die stap-oorslaan-logika - die hersieningsbladsy wat slegs verskyn wanneer totaal >= 100 - is ingebed in die showSubmit-veranderlike en die leweringvoorwaarde op stap 3. Navigasie self is net 'n useState-teller wat ons handmatig verhoog. React Query hanteer herproberings, kasgeheue en ongeldigmaking. Die vorm roep net mutation.mutate met gevalideerde data.
Niks hiervan is per se verkeerd nie. Dit is steeds idiomatiese React, en die komponent is redelik werksaam danksy hoe RHF herweergawes isoleer. Maar as jy dit sou oorhandig aan iemand wat dit nie geskryf het nie en hulle vra om te verduidelik onder watter omstandighede die resensiebladsy verskyn, sal hulle moet naspeur deur showSubmit, die stap 3-weergawevoorwaarde en die nav-knoppie-logika - drie afsonderlike plekke - om 'n reël te rekonstrueer wat in een reël gestel kon gewees het. Die vorm werk, ja, maar die gedrag is nie regtig inspekteerbaar as 'n stelsel nie. Dit moet geestelik uitgevoer word. Nog belangriker, om dit te verander, vereis ingenieursbetrokkenheid. Selfs 'n klein aanpassing, soos om aan te pas wanneer die hersieningstap verskyn, beteken om die komponent te wysig, validering op te dateer, 'n trekversoek oop te maak, vir hersiening te wag en weer te ontplooi. Deel 2: Skema-gedrewe (SurveyJS) Kom ons bou nou dieselfde vloei deur 'n skema te gebruik. Installasie npm installeer survey-core survey-react-ui @tanstack/react-query
survey-core Die MIT-gelisensieerde platformonafhanklike runtime-enjin wat SurveyJS se vormweergawe aandryf - die deel waaroor ons hier omgee. Dit neem 'n JSON-skema, bou 'n interne model daaruit en hanteer alles wat andersins in jou React-komponent sou woon: die evaluering van sigbaarheidsuitdrukkings, die berekening van afgeleide waardes, die bestuur van bladsystatus, die dop van validering, en besluit wat "voltooi" beteken, gegewe watter bladsye werklik gewys is.
survey-react-uiDie UI / weergawe laag wat daardie model aan React verbind. Dit is in wese 'n
Saam gee hulle jou 'n ten volle funksionele, multi-bladsy vorm looptyd sonder om 'n enkele reël van beheervloei te skryf. Die skema-formaat self is, soos voorheen gesê, net 'n JSON - geen DSL of enigiets eie nie. Jy kan dit inlyn, dit vanaf 'n lêer invoer, dit van 'n API gaan haal, of dit in 'n databasiskolom stoor en dit tydens looptyd hidreer. Dieselfde vorm, as data Hier is dieselfde vorm, hierdie keer uitgedruk as 'n JSON-voorwerp. Die skema definieer alles: struktuur, validering, sigbaarheidsreëls, afgeleide berekeninge, bladsynavigasie - en gee dit aan 'n model wat dit tydens looptyd evalueer. Hier is hoe dit ten volle lyk:
export const surveySchema = { title: "Order Flow", showProgressBar: "top", bladsye: [ { name: "details", elements: [ { type: "text", name: "firstName", isRequired: true }, { type: "text", name: "email", inputType: "email", isRequired: true, "validers": [{ type:"] }, { naam: "order", elemente: [ { tipe: "teks", naam: "prys", invoertipe: "nommer", verstekwaarde: 0 }, {tipe: "teks", naam: "hoeveelheid", invoertipe: "nommer", verstekwaarde: 1 }, {tipe: "aftreklys",naam: "belastingtarief", verstekWaarde: 0.1, keuses: [ { waarde: 0.05, teks: "5%" }, { waarde: 0.1, teks: "10%" }, { waarde: 0.15, teks: "15%" } ] }, { tipe: "uitdrukking", naam: "subtotaal" {, subtotaal}} uitdrukking: {, subtotaal}} uitdrukking: "uitdrukking", naam: "belasting", uitdrukking: "{subtotaal} {belastingtarief}" }, { tipe: "uitdrukking", naam: "totaal", uitdrukking: "{subtotaal} + {belasting}" } ] }, { naam: "rekening", elemente: [ { tipe: "radiogroep", naam: "het Rekening", keuses: ["] "Ja", "Nee tipe", "naam ", "naam ", "naam visibleIf: "{hasAccount} = 'Ja'", isVereis: waar }, { tipe: "teks", naam: "wagwoord", inputType: "wagwoord", visibleIf: "{hasAccount} = 'Ja'", is Vereis: waar, valideerders: [{ tipe: "teks", minLength: "6, karakters: "Min: tik" ] } naam, {} "satisfaction", rateMin: 1, rateMaks: 5 }, { tipe: "comment", naam: "positiveFeedback", visibleIf: "{satisfaction} >= 4" }, { type: "comment", naam: "verbeteringTerugvoer", sigbaarAs: "{satisfaction} <= 2} "f re: ", {}tal to bekyk" } ] >= 100", elemente: [] } ]};
Vergelyk dit vir 'n oomblik met die RHF-weergawe.
Die superRefine-blok wat voorwaardelik die gebruikersnaam en wagwoord vereis het, is weg. visibleIf: "{hasAccount} = 'Ja'" gekombineer met isRequired: true hanteer beide bekommernisse saam, op die veld self, waar jy sou verwag om hulle te vind. Die useWatch + useMemo-ketting wat subtotaal, belasting en totaal bereken het, word vervang deur drie uitdrukkingsvelde wat na mekaar verwys. Die hersieningsbladsy-toestand, wat in die RHF-weergawe slegs rekonstrueerbaar was deur na te spoor deur showSubmit, die stap 3-weergawetak. En laastens, die nav-knoppie-logika is 'n enkele visibleIf-eienskap op die bladsy-objek.
Dieselfde logika is daar. Dit is net dat die skema dit 'n plek gee om te woon waar dit in isolasie sigbaar is, eerder as om oor die komponent te versprei. Let ook daarop dat die skema tipe gebruik: 'uitdrukking' vir subtotaal, belasting en totaal. Uitdrukking is leesalleen en word hoofsaaklik gebruik om berekende waardes te vertoon. SurveyJS ondersteun ook tipe: 'html' vir statiese inhoud, maar vir berekende waardes is uitdrukking die regte keuse. Nou vir die Reageer-kant. Weerlewering en Voorlegging Baie eenvoudig. Bedraad onComplete na jou API op dieselfde manier – via useMutation of gewone haal:
invoer { useState, useEffect, useRef } vanaf "react"; voer { useMutation } van "@tanstack/react-query" in; voer { Model } van "survey-core" in; voer { Survey } van "survey-react-ui" in; voer "survey-core/survey"-in in;
uitvoerfunksie SurveyForm() { const [model] = useState(() => nuwe Model(surveySkema));
const mutation = useMutation({ mutationFn: asinc (data) => { const res = wag op haal("/api/orders", { metode: "POS", headers: { "Content-Type": "application/json" }, liggaam: JSON.stringify(data), }); if (!res.ok) gooi nuwe Fout ("Kon nie indien nie"); gee res.json(); }, });
const mutationRef = useRef(mutasie); mutationRef.current = mutasie; useEffect(() => { const hanteerder = (sender) => mutationRef.current.mutate(sender.data); model.onComplete.add(handler); return () => model.onComplete.remove(handler); }, [model]); // ref vermy die herregistrasie van hanteerder elke weergawe (mutasie voorwerp identiteit verander)
keer terug (
<>
Sien die Pen SurveyJS-03-SurveyJS [gevurk] deur sixthextinction.
onComplete brand wanneer die gebruiker die einde van die laaste sigbare bladsy bereik. So as die totaal nooit 100 oorskry nie en die resensiebladsy word oorgeslaan, brand dit steeds korrek omdat SurveyJS sigbaarheid evalueer voordat daar besluit word wat "laaste bladsy" beteken. Sender.data bevat dan alle antwoorde saam met die berekende waardes (subtotaal, belasting, totaal) as eersteklas-velde, so die API-loonvrag is identies aan wat die RHF-weergawe handmatig in onSubmit saamgestel het. DiemutationRef-patroon is dieselfde een waarheen jy sal bereik waar jy 'n stabiele gebeurtenishanteerder nodig het oor 'n waarde wat op elke weergawe verander - niks SurveyJS-spesifiek daaroor nie.
Die React-komponent bevat glad nie meer enige besigheidslogika nie. Daar is geen useWatch, geen voorwaardelike JSX, geen stapteller, geen useMemo-ketting, geen superRefine. React doen waarmee hy eintlik goed is: 'n komponent weergee en dit aan 'n API-oproep verbind. Wat het uit reaksie beweeg?
Bekommernis RHF stapel SurveyJS Sigbaarheid JSX takke sigbaar As Afgeleide waardes useWatch / useMemo uitdrukking Kruisveldreëls superVerfyn Skema voorwaardes Navigasie stap staat Bladsy sigbaarIndien Reël ligging Versprei oor lêers Gesentraliseer in die skema
Wat in React bly, is uitleg, stilering, voorleggingsbedrading en app-integrasie, wat wil sê, waarvoor React eintlik ontwerp is. Al die ander dinge het in die skema ingeskuif, en omdat die skema net 'n JSON-objek is, kan dit in 'n databasis gestoor word, onafhanklik van jou toepassingskode versier word, of deur interne gereedskap geredigeer word sonder dat 'n ontplooiing vereis word. 'n Produkbestuurder wat die drempel wat die hersieningsbladsy aktiveer, moet verander, kan dit doen sonder om aan die komponent te raak. Dit is 'n betekenisvolle operasionele verskil vir spanne waar vormgedrag gereeld ontwikkel en nie altyd deur ingenieurs gedryf word nie. Wanneer om elke benadering te gebruik? Hier is 'n goeie reël wat vir my werk: stel jou voor dat jy die vorm heeltemal uitvee. Wat sou jy verloor?
As dit skerms is, wil jy komponentgedrewe vorms hê. As dit besigheidslogika is, soos drempels, vertakkingsreëls en voorwaardelike vereistes wat regte besluite kodeer, wil jy 'n skema-enjin hê.
Net so, as die veranderinge wat na jou kant toe kom meestal oor etikette, velde en uitleg gaan, sal RHF jou goed dien. As dit gaan oor toestande, uitkomste en reëls wat jou ops of regspan dalk op 'n Dinsdagmiddag moet aanpas sonder om 'n kaartjie in te dien, is die skemamodel met SurveyJS die eerliker pas. Hierdie twee benaderings is nie werklik in kompetisie met mekaar nie. Hulle spreek verskillende klasse probleme aan, en die fout wat die moeite werd is om te vermy, is om die abstraksie nie te pas by die gewig van die logika nie - om 'n reëlstelsel soos 'n komponent te behandel omdat dit die bekende instrument is, of om na 'n beleidsenjin te gryp omdat 'n vorm tot drie stappe gegroei het en 'n voorwaardelike veld verkry het. Die vorm wat ons hier gebou het, sit doelbewus naby die grens, kompleks genoeg om die verskil bloot te lê, maar nie so ekstreem dat die vergelyking geknou voel nie. Die meeste werklike vorms wat in jou kodebasis moeilik geword het, sit waarskynlik naby dieselfde grens, en die vraag is gewoonlik net of iemand genoem het wat hulle werklik is. Gebruik React Hook Form + Zod wanneer:
Vorms is CRUD-georiënteerd; Logika is vlak en UI-gedrewe; Ingenieurs besit alle gedrag; Backend bly die bron van waarheid.
Gebruik SurveyJS wanneer:
Vorms kodeer besigheidsbesluite; Reëls ontwikkel onafhanklik van UI; Logika moet sigbaar, ouditeerbaar of weergawe wees; Nie-ingenieurs beïnvloed gedrag; Dieselfde vorm moet oor verskeie frontends loop.