Ĉi tiu artikolo estas sponsorita de SurveyJS Estas mensa modelo, kiun plej multaj programistoj de React dividas sen iam diskuti ĝin laŭte. Ke formoj ĉiam supozeble estas komponantoj. Ĉi tio signifas stakon kiel:
React Hook Form por loka ŝtato (minimumaj re-bildoj, ergonomia kampo-registrado, imperativa interago). Zod por validumado (eniga korekteco, limvalidigo, tip-sekura analizo). Reagi Demandon por backend: submetado, reprovoj, kaŝmemoro, servila sinkronigo, ktp.
Kaj por la granda plimulto de formoj - viaj ensalut-ekranoj, viaj agordaj paĝoj, viaj CRUD-modaloj - tio funkcias vere bone. Ĉiu peco faras sian laboron, ili komponas pure, kaj vi povas pluiri al la partoj de via aplikaĵo, kiuj efektive diferencas vian produkton. Sed de tempo al tempo, formo komencas amasigi aferojn kiel videblecoj, kiuj dependas de pli fruaj respondoj, aŭ derivitaj valoroj, kiuj kaskadas tra tri kampoj. Eble eĉ tutaj paĝoj kiuj devus esti preterlasitaj aŭ montritaj surbaze de kuranta totalo. Vi pritraktas la unuan kondiĉon per useWatch kaj enlinia branĉo, kio estas bone. Poste alia. Tiam vi serĉas superRefine por kodi transkampajn regulojn, kiujn via Zod-skemo ne povas esprimi laŭ la normala maniero. Tiam, paŝa navigado komencas liki komercan logikon. Iam vi rigardas tion, kion vi konstruis kaj rimarkas, ke la formo ne plu estas vere UI. Ĝi estas pli de decida procezo, kaj la kompona arbo estas ĝuste kie vi hazarde konservis ĝin. Jen kie mi pensas, ke la mensa modelo por formoj en React rompiĝas, kaj ĝi estas vere neniu kulpo. La stako RHF + Zod estas bonega pri tio, por kio ĝi estis desegnita. La afero estas, ke ni emas daŭre uzi ĝin preter la punkto kie ĝiaj abstraktaĵoj kongruas kun la problemo ĉar la alternativo postulas tute malsaman manieron pensi pri formoj. Ĉi tiu artikolo temas pri tiu alternativo. Por montri ĉi tion, ni konstruos la ĝustan saman plurpaŝan formon dufoje:
Kun React Hook Form + Zod kablita al React Query por submetado, Kun SurveyJS, kiu traktas formon kiel datumojn - simplan JSON-skemon - prefere ol komponan arbon.
Samaj postuloj, sama kondiĉa logiko, sama API-voko ĉe la fino. Tiam ni mapos precize kio moviĝis kaj kio restis, kaj aranĝos praktikan manieron decidi kiun modelon vi devas uzi, kaj kiam. La formo, kiun ni konstruas:
Ĉi tiu formo uzos 4-paŝan fluon: Paŝo 1: Detaloj
Antaŭnomo (postulata), Retpoŝto (postulata, valida formato).
Paŝo 2: Ordonu
Unua prezo, Kvanto, Imposta indico, Derivita: Subtotalo, Imposto, Entute.
Paŝo 3: Konto kaj Reago
Ĉu vi havas konton? (Jes/Ne) Se Jes → uzantnomo + pasvorto, ambaŭ necesas. Se Ne → retpoŝto jam kolektita en paŝo 1.
Kontentiga takso (1–5) Se ≥ 4 → demandu "Kion vi ŝatis?" Se ≤ 2 → demandu "Kion ni povas plibonigi?"
Paŝo 4: Revizio
Nur aperas se entute >= 100 Fina submetiĝo.
Ĉi tio ne estas ekstrema. Sed sufiĉas elmontri arkitekturajn diferencojn. Parto 1: Komponanto-Movita (Reagi Hoko-Formo + Zod) Instalado npm instali react-hook-form zod @hookform/resolvers @tanstack/react-query
Zod-Skemo Ni komencu per la Zod-skemo, ĉar tie kutime stariĝas la formo de la formo. Por la unuaj du paŝoj — personaj detaloj kaj mendaj enigaĵoj — ĉio estas simpla: postulataj ĉenoj, nombroj kun minimumoj kaj enumo. La interesa parto komenciĝas kiam oni provas esprimi la kondiĉajn regulojn.
importi { z } el "zod";
eksporto const formSchema = z.object({ firstName: z.string().min(1, "Bezonata"), retpoŝto: z.string().email("Nevalida retpoŝto"), prezo: z.number().min(0), kvanto: z.number().min(1), taxRate: z.number(), hasKonto: z.number(), has Account (No["]), uzantnomo (No["]), "z.esum" z.string().optional(), pasvorto: z.string().optional(), kontentigo: z.number().min(1).max(5), pozitivaResago: z.string().optional(), plibonigoResago: z.string().optional(),}).superRefine((datumoj, ctx) => { if (datenoj, ctx) => { if (datenoj, ctx) => { if (datenoj, ctx) => { if (datumoj) === "Yes Account. ctx.addIssue({ kodo: "persona", vojo: ["uzantnomo"], mesaĝo: "Bezonata" }); } if (!data.password || data.password.length < 6) { ctx.addIssue({ kodo: "persona", vojo: ["pasvorto"], mesaĝo: "Min 6 signoj}"});
if (data.satisfaction >= 4 && !data.positiveFeedback) { ctx.addIssue({ kodo: "persona", vojo: ["positiveFeedback"], mesaĝo: "Bonvolu dividi kion vi ŝatis" }); }
if (data.satisfaction <= 2 && !data.improvementFeedback) { ctx.addIssue ({ kodo: "persona", vojo:["improvementFeedback"], message: "Bonvolu diri al ni kion plibonigi"}); }});
eksporta tipo FormData = z.infer
Rimarku, ke uzantnomo kaj pasvorto estas tajpitaj kiel laŭvolaj () kvankam ili estas kondiĉe postulataj ĉar la tipnivela skemo de Zod priskribas la formon de la objekto, ne la regulojn regantajn kiam kampoj gravas. La kondiĉa postulo devas vivi ene de superRefine, kiu funkcias post kiam la formo estas validigita kaj havas aliron al la plena objekto. Tiu disiĝo ne estas manko; ĝi estas nur por kio la ilo estas dizajnita: superRefine estas kie transkampa logiko iras kiam ĝi ne povas esti esprimita en la skemstrukturo mem. Kio estas ankaŭ rimarkinda ĉi tie estas tio, kion ĉi tiu skemo ne esprimas. Ĝi havas neniun koncepton de paĝoj, neniu koncepto de kiuj kampoj estas videblaj ĉe kiu punkto, kaj neniu koncepto de navigado. Ĉio tio loĝos aliloke. Forma Komponanto
importu { useForm, useWatch } el "react-hook-form";importu { zodResolver } el "@hookform/resolvers/zod";importu { useMutation } el "@tanstack/react-query";importu { useState, useMemo } el "react";import { formSchema, tajpu FormDataschema};
const STEPS = ["detaloj", "mendo", "konto", "recenzo"];
type OrderPayload = FormData & { subtotalo: nombro; imposto: nombro; totalo: nombro };
eksportfunkcio RHFMultiStepForm () { const [step, setStep] = uzoStato (0);
konst mutacio = uzuMutacion({ mutationFn: nesinkronigita (utila ŝarĝo: OrderPayload) => { const res = atendi preni ("/api/mendoj", { metodo: "POST", kaplinioj: { "Content-Type": "application/json" }, korpo: JSON.stringify(utila ŝarĝo), }); if (!res.ok) throw new Eraro ("Malsukcesis sendi"); resendi res.json(); }, });
const { registri, kontrolo, manipuliSubmit, formState: { eraroj }, } = useForm
return (
);}Vidu la Pen SurveyJS-03-RHF [forkigita] de sixtheextinction. Ĉi tie okazas sufiĉe multe, kaj indas malrapidiĝi por rimarki, kie aferoj finiĝis.
La derivitaj valoroj - subtotalo, imposto, totalo - estas kalkulitaj en la komponanto per useWatch kaj useMemo ĉar ili dependas de vivaj kampaj valoroj kaj ne ekzistas alia natura loko por ili. La reguloj pri videbleco por uzantnomo, pasvorto, pozitiva Reago kaj plibonigoResago vivas en JSX kiel enliniaj kondicionaloj. La paŝ-salta logiko — la revizia paĝo aperas nur kiam entute >= 100 — estas enigita en la showSubmit-variablo kaj la redona kondiĉo en la paŝo 3. Navigado mem estas nur useState-nombrilo, kiun ni mane pliigas. React Query pritraktas reprovojn, kaŝmemoron kaj malvalidigon. La formo nur vokas mutation.mutate kun validigitaj datumoj.
Nenio el ĉi tio estas malĝusta, en si mem. Ĉi tio ankoraŭ estas idioma React, kaj la komponanto estas sufiĉe efika danke al kiel RHF izolas reprezentojn. Sed se vi donus ĉi tion al iu, kiu ne skribis ĝin kaj petus, ke ili klarigu sub kiaj kondiĉoj aperas la revizia paĝo, ili devus spuri per showSubmit, la paŝo 3-a renderkondiĉo kaj la navbutonlogiko - tri apartaj lokoj - por rekonstrui regulon kiu povus esti deklarita en unu linio. La formo funkcias, jes, sed la konduto ne estas vere inspektebla kiel sistemo. Ĝi devas esti ekzekutita mense. Pli grave, ŝanĝi ĝin postulas inĝenieran implikiĝon. Eĉ malgranda tajlado, kiel ĝustigi kiam la revizia paŝo aperas, signifas redakti la komponanton, ĝisdatigi validigon, malfermi tiran peton, atendi revizion kaj denove deploji. Parto 2: Skemo-Instruita (SurveyJS) Nun ni konstruu la saman fluon uzante skemon. Instalado npm instalu survey-core survey-react-ui @tanstack/react-query
survey-coreLa MIT-licencita platformo-sendependa rultempa motoro kiu funkciigas la formbildigon de SurveyJS - la parto pri kiu ni zorgas ĉi tie. Ĝi prenas JSON-skemon, konstruas internan modelon el ĝi, kaj pritraktas ĉion, kio alie vivus en via React-komponento: taksi videblecajn esprimojn, komputi derivitajn valorojn, administri paĝan staton, spuri validigon kaj decidi kion signifas "kompleta" laŭ kiuj paĝoj estis efektive montritaj.
survey-react-uiLa UI/bildiga tavolo kiu ligas tiun modelon al React. Ĝi estas esence
Kune, ili donas al vi plene funkcian, plurpaĝan formularan rultempon sen skribi ununuran linion de kontrolfluo. La skemformato mem estas, kiel dirite antaŭe, nur JSON - neniu DSL aŭ io ajn proprieta. Vi povas enlinii ĝin, importi ĝin de dosiero, preni ĝin de API aŭ konservi ĝin en datumbaza kolumno kaj hidratigi ĝin ĉe rultempo. La Sama Formo, Kiel Datumoj Jen la sama formo, ĉi-foje esprimita kiel JSON-objekto. La skemo difinas ĉion: strukturo, validumado, videbleco-reguloj, derivitaj kalkuloj, paĝnavigado - kaj transdonas ĝin al Modelo, kiu taksas ĝin ĉe rultempo. Jen kiel tio aspektas tute:
export const surveySchema = { title: "Order Flow", showProgressBar: "top", paĝoj: [ { name: "details", elements: [ { type: "text", name: "firstName", isRequired: true }, { type: "text", name: "retpoŝto", inputType: "retpoŝto", isRequired: true, validators: [{} type: "net email"] text nomo: "ordo", elementoj: [ { type: "text", name: "price", inputType: "number", defaultValue: 0 }, { type: "text", name: "kvanto", inputType: "number", defaultValue: 1 }, { type: "dropdown",nomo: "taxRate", defaultValue: 0.1, elektoj: [ { valoro: 0.05, teksto: "5%" }, { valoro: 0.1, teksto: "10%" }, { valoro: 0.15, teksto: "15%" } ] }, { type: "esprimo", nomo: "subtotalo",} esprimo: "kvanto:{}"} esprimo: "kvanto:{}"} esprimo: "kvanto:}"} esprimo "imposto", esprimo: "{subtotalo} {taxRate}" }, { tipo: "esprimo", nomo: "tuto", esprimo: "{subtotalo} + {imposto}" } ] }, { nomo: "konto", elementoj: [ { type: "radiogrupo", nomo: "hasAccount", elektoj: ["Jes", "Ne"] }, nomo: "f: ni-nomo" }, {name: "f. "{hasAccount} = 'Jes'", isRequired: true }, { type: "text", name: "password", inputType: "password", visibleIf: "{hasAccount} = 'Yes'", isRequired: true, validigiloj: [{ type: "text", minLength: 6, text: "Min 6 characters}," {:] ", tajpaĵoj," {:] ", tajpaĵoj rateMin: 1, rateMax: 5 }, { type: "komento", nomo: "positiveFeedback", visibleIf: "{kontentigo} >= 4" }, { type: "komento", name: "improvementFeedback", visibleIf: "{kontentigo} <= 2" } ] }, {I}= "respekto", {I== "respekto", {I== "respekto", {I=: 0 "elementoj", {1}= "revido" [] } ]};
Komparu ĉi tion al la RHF-versio por momento.
La superRefine-bloko kiu kondiĉe postulis uzantnomon kaj pasvorton malaperis. visibleIf: "{hasAccount} = 'Jes'" kombinita kun isRequired: true traktas ambaŭ zorgojn kune, sur la kampo mem, kie vi atendus trovi ilin. La ĉeno useWatch + useMemo kiu kalkulis subtotalon, imposton kaj totalon estas anstataŭigita per tri esprimkampoj kiuj referencas unu la alian per nomo. La revizia paĝa kondiĉo, kiu en la RHF-versio estis rekonstruebla nur per spurado per showSubmit, la paŝo 3-a bildigi branĉon. Kaj finfine, la nav-butono-logiko estas ununura videblaIf-posedaĵo sur la paĝa objekto.
La sama logiko estas tie. Estas nur ke la skemo donas al ĝi lokon por vivi kie ĝi estas videbla izole, anstataŭ disvastigita tra la komponanto. Ankaŭ, notu, ke la skemo uzas tipon: 'esprimo' por subtotalo, imposto kaj totalo. Esprimo estas nurlegebla kaj uzata ĉefe por montri kalkulitajn valorojn. SurveyJS ankaŭ subtenas tipon: 'html' por senmova enhavo, sed por kalkulitaj valoroj, esprimo estas la ĝusta elekto. Nun por la React-flanko. Redonado Kaj Submetiĝo Tre simpla. Konektu onComplete al via API sammaniere - per uzoMutacio aŭ simpla preni:
importi { useState, useEffect, useRef } el "reakci";importi { useMutation } el "@tanstack/react-query";importi { Modelon } el "survey-core";importi { Survey } el "survey-react-ui";importi "survey-core/survey-core.css";
eksportfunkcio EnketoFormo () { const [modelo] = uzoStato (() => nova Modelo (enketoSkemo));
konst mutacio = uzuMutacion({ mutationFn: nesinkronigita (datenoj) => { const res = atendi preni ("/api/mendoj", { metodo: "POST", kaplinioj: { "Content-Type": "application/json" }, korpo: JSON.stringify(datenoj), }); if (!res.ok) throw new Eraro ("Malsukcesis sendi"); resendi res.json(); }, });
const mutacioRef = uzoRef (mutacio); mutationRef.current = mutacio; useEffect (() => { const pritraktilo = (sendanto) => mutacioRef.current.mutate (sendinto.datumoj); modelo.onComplete.add (traktilo); return () => modelo.onComplete.remove (traktilo); }, [modelo]); // ref evitas reregistri pritraktilon ĉiun bildigon (ŝanĝoj de identeco de la objekto de mutacio)
reveni (
<>
Vidu la Pen SurveyJS-03-SurveyJS [forkigita] de sixthextinction.
onComplete ekfunkciigas kiam la uzanto atingas la finon de la lasta videbla paĝo. Do se totalo neniam transiras 100 kaj la revizia paĝo estas preterlasita, ĝi ankoraŭ pafas ĝuste ĉar SurveyJS taksas videblecon antaŭ ol decidi kion signifas "lasta paĝo". Tiam, sender.data enhavas ĉiujn respondojn kune kun la kalkulitaj valoroj (subtotalo, imposto, totalo) kiel unuaklasaj kampoj, do la API-utila ŝarĝo estas identa al tio, kion la RHF-versio kunvenis permane en onSubmit. LamutationRef-ŝablono estas la sama, kiun vi atingus ie ajn, kie vi bezonas stabilan okazaĵan pritraktilon super valoro, kiu ŝanĝiĝas je ĉiu bildigo - nenio specifa de SurveyJS pri ĝi.
La React-komponento tute ne enhavas komercan logikon. Ne ekzistas uzoWatch, neniu kondiĉa JSX, neniu paŝokalkulilo, neniu uzoMemoĉeno, neniu superrafinado. React faras tion, pri kio ĝi vere kapablas: bildigi komponenton kaj kabligi ĝin al API-voko. Kio Movis El Reagi?
Koncerno RHF-Stako EnketoJS Videbleco JSX-filioj videblaSe Devenaj valoroj uzu Rigardu / uzuMemon esprimo Interkampaj reguloj superrafini Skemaj kondiĉoj Navigado paŝo stato Paĝo videblaSe Regulo loko Distribuita tra dosieroj Centrigita en la skemo
Kio restas en React estas aranĝo, stilo, submetado kaj aplika integriĝo, tio estas, la aferoj por kiuj React estas fakte desegnita. Ĉio alia moviĝis en la skemon, kaj ĉar la skemo estas nur JSON-objekto, ĝi povas esti stokita en datumbazo, versionita sendepende de via aplika kodo, aŭ redaktita per interna ilaro sen postulo de deplojo. Produktmanaĝero, kiu bezonas ŝanĝi la sojlon, kiu ekigas la revizian paĝon, povas fari tion sen tuŝi la komponanton. Tio estas signifa funkcia diferenco por teamoj, kie formkonduto ofte evoluas kaj ne ĉiam estas gvidata de inĝenieroj. Kiam Uzi Ĉiun Aliron? Jen bona regulo, kiu funkcias por mi: imagu tute forigi la formularon. Kion vi perdus?
Se ĝi estas ekranoj, vi volas komponantajn formojn. Se ĝi estas komerca logiko, kiel sojloj, disbranĉaj reguloj kaj kondiĉaj postuloj, kiuj ĉifras realajn decidojn, vi volas skemmotoron.
Simile, se la ŝanĝoj venantaj al vi estas plejparte pri etikedoj, kampoj kaj aranĝo, RHF bone servos al vi. Se temas pri kondiĉoj, rezultoj kaj reguloj, kiujn via operaciaro aŭ jura teamo eble bezonos ĝustigi mardon posttagmeze sen prezenti bileton, la skemo-modelo kun SurveyJS estas la pli honesta taŭga. Ĉi tiuj du aliroj ne vere konkuras unu kun la alia. Ili traktas malsamajn klasojn de problemoj, kaj la eraro evitinda estas miskongruo de la abstraktado al la pezo de la logiko - trakti regulsistemon kiel komponenton ĉar tio estas la konata ilo, aŭ atingi politikan motoron ĉar formo kreskis al tri ŝtupoj kaj akiris kondiĉan kampon. La formo, kiun ni konstruis ĉi tie, sidas proksime de la limo intence, sufiĉe kompleksa por elmontri la diferencon, sed ne tiom ekstrema, ke la komparo sentas sin rigita. Plej multaj realaj formoj, kiuj malfaciliĝis en via kodbazo, verŝajne sidas proksime de tiu sama limo, kaj la demando estas kutime nur ĉu iu nomis tion, kion ili efektive estas. Uzu React Hook Form + Zod kiam:
Formoj estas CRUD-orientitaj; Logiko estas malprofunda kaj UI-movita; Inĝenieroj posedas ĉian konduton; Backend restas la fonto de vero.
Uzu SurveyJS kiam:
Formoj ĉifras komercajn decidojn; Reguloj evoluas sendepende de UI; Logiko devas esti videbla, aŭdebla aŭ versionita; Ne-inĝenieroj influas konduton; La sama formo devas ruliĝi tra pluraj fasadoj.