Šį straipsnį remia SurveyJS Yra psichikos modelis, kuriuo dauguma „React“ kūrėjų dalijasi niekada garsiai neaptardami. Kad formos visada turi būti komponentai. Tai reiškia krūvą, pavyzdžiui:
„React Hook“ forma, skirta vietinei valstijai (minimalus pakartotinis atvaizdavimas, ergonomiška lauko registracija, būtina sąveika). Zod patvirtinimui (įvesties teisingumas, ribos patvirtinimas, saugaus tipo analizė). Reaguoti į užklausą užpakalinei programai: pateikimas, bandymai pakartotinai, talpyklos kaupimas, serverio sinchronizavimas ir pan.
Ir daugumoje formų – prisijungimo ekranuose, nustatymų puslapiuose, CRUD modaluose – tai veikia tikrai gerai. Kiekvienas kūrinys atlieka savo užduotį, jis puikiai komponuojamas, o jūs galite pereiti prie programos dalių, kurios iš tikrųjų išskiria jūsų produktą. Tačiau retkarčiais formoje pradedami kaupti dalykai, pvz., matomumo taisyklės, kurios priklauso nuo ankstesnių atsakymų, arba išvestinės reikšmės, kurios perkeliamos per tris laukus. Galbūt net ištisus puslapius, kuriuos reikėtų praleisti arba rodyti pagal einamąją sumą. Pirmąją sąlyginę sąlygą tvarkote naudodami „useWatch“ ir įterptą šaką, o tai gerai. Tada kitas. Tada jūs siekiate „superRefine“, kad užkoduotų kryžminio lauko taisykles, kurių jūsų „Zod“ schema negali išreikšti įprastu būdu. Tada žingsnis navigacija pradeda nutekėti verslo logika. Tam tikru momentu pažvelgi į tai, ką sukūrei, ir supranti, kad forma nebėra vartotojo sąsaja. Tai daugiau sprendimų priėmimo procesas, o komponentų medis yra kaip tik ten, kur jį saugojote. Čia, manau, sugenda „React“ formų mentalinis modelis, ir dėl to tikrai niekas nėra kaltas. RHF + Zod kamino puikiai tinka tam, kam jis buvo sukurtas. Problema ta, kad mes linkę jį naudoti po to, kai jo abstrakcijos atitinka problemą, nes alternatyva reikalauja visiškai kitokio mąstymo apie formas. Šis straipsnis yra apie šią alternatyvą. Norėdami tai parodyti, du kartus sukursime tą pačią kelių pakopų formą:
Su React Hook Form + Zod prijungta prie React Query pateikimo, Su SurveyJS, kuri formą traktuoja kaip duomenis – paprastą JSON schemą, o ne kaip komponentų medį.
Tie patys reikalavimai, ta pati sąlyginė logika, tas pats API iškvietimas pabaigoje. Tada tiksliai nustatysime, kas persikėlė, o kas liko, ir pateiksime praktinį būdą, kaip nuspręsti, kurį modelį ir kada naudoti. Forma, kurią kuriame:
Šioje formoje bus naudojamas 4 žingsnių srautas: 1 veiksmas: išsami informacija
Vardas (būtina), paštas (būtina, galiojantis formatas).
2 veiksmas: užsakykite
Vieneto kaina, Kiekis, Mokesčio tarifas, Išvesta: Tarpinė suma, Mokesčiai, Iš viso.
3 veiksmas: paskyra ir atsiliepimai
Ar turite paskyrą? (Taip/Ne) Jei Taip → vartotojo vardas + slaptažodis, abu reikalingi. Jei ne → el. laiškas jau surinktas atliekant 1 veiksmą.
Pasitenkinimo įvertinimas (1–5) Jei ≥ 4 → paklauskite „Kas tau patiko? Jei ≤ 2 → paklauskite „Ką galime patobulinti?
4 veiksmas: peržiūra
Rodoma tik tada, jei iš viso >= 100 Galutinis pateikimas.
Tai nėra ekstremalu. Tačiau užtenka atskleisti architektūrinius skirtumus. 1 dalis: Komponentai varomi (React Hook Form + Zod) Montavimas npm įdiegti react-hook-form zod @hookform/resolvers @tanstack/react-query
Zod schema Pradėkime nuo Zod schemos, nes čia dažniausiai nustatoma formos forma. Atliekant pirmuosius du veiksmus – asmeninę informaciją ir užsakymo įvestis – viskas paprasta: būtinos eilutės, skaičiai su minimumu ir eilė. Įdomiausia dalis prasideda, kai bandote išreikšti sąlygines taisykles.
importuoti { z } iš "zod";
export const formSchema = z.object({ vardas: z.string().min(1, "Reikalingas"), el. pašto adresas: z.string().email("Neteisingas el. pašto adresas"), kaina: z.numeris().min(0), kiekis: z.numeris().min(1), taxRate: z.number(), hasPaskyra: "z.numeris(), hasPaskyra: ["],enum z.string().optional(), slaptažodis: z.string().neprivalomas(), pasitenkinimas: z.skaičius().min(1).maks.(5), teigiamasAtsiliepimas: z.string().neprivalomas(), tobulinimasAtsiliepimas: z.string().optional(),}).superRefine((duomenys, ctxdata) =.=.ha { if (ctxda) =. (!data.username) { ctx.addIssue({ kodas: "custom", kelias: ["naudotojo vardas"], pranešimas: "Reikalingas" }); } if (!data.slaptažodis || data.password.length < 6) { ctx.addIssue({ code: "custom" , kelias:]); } }
if (data.satisfaction >= 4 && !data.positiveFeedback) { ctx.addIssue({ kodas: "custom", kelias: ["teigiamas atsiliepimas"], pranešimas: "Prašome pasidalinti tuo, kas jums patiko" }); }
if (data.satisfaction <= 2 && !data.improvementFeedback) { ctx.addIssue({ kodas: "custom", kelias:["improvementFeedback"], pranešimas: "Pasakykite mums, ką reikia patobulinti" }); }});
eksporto tipas FormData = z.infer
Atkreipkite dėmesį, kad naudotojo vardas ir slaptažodis įvedami kaip pasirinktiniai (), net jei jie sąlyginai reikalingi, nes Zod tipo lygio schema apibūdina objekto formą, o ne taisykles, reglamentuojančias, kada svarbūs laukai. Sąlyginis reikalavimas turi būti vykdomas „superRefine“, kuris vykdomas patvirtinus formą ir turi prieigą prie viso objekto. Tas atskyrimas nėra trūkumas; kaip tik tam įrankis skirtas: „superRefine“ yra ta vieta, kur naudojama kryžminio lauko logika, kai jos negalima išreikšti pačioje schemos struktūroje. Čia taip pat svarbu tai, ko ši schema neišreiškia. Jame nėra puslapių sąvokos, nėra sąvokos, kurie laukai kurioje vietoje matomi, ir nėra naršymo koncepcijos. Visa tai gyvens kitur. Formos komponentas
importuoti { useForm, useWatch } iš "react-hook-form";importuoti { zodResolver } iš "@hookform/resolvers/zod";importuoti { useMutation } iš "@tanstack/react-query";importuoti { useState, useMemo } iš "react";importuoti { formSchema"};
const STEPS = ["detaliai", "užsakymas", "sąskaita", "peržiūra"];
tipo OrderPayload = FormData & { tarpinė suma: skaičius; mokestis: numeris; iš viso: skaičius };
eksportavimo funkcija RHFMultiStepForm() { const [žingsnis, setStep] = useState(0);
const mutacija = useMutation({ mutationFn: async (naudinga apkrova: OrderPayload) => { const res = laukti fetch("/api/orders", { metodas: "POST", antraštės: { "Content-Type": "application/json" }, kūnas: JSON.stringify(naudinga apkrova), }); if (!res.ok) throw new Error("Nepavyko pateikti"); return res.json(); }, });
const { register, control, handleSubmit, formState: { errors }, } = useForm
return (
);}Žr. rašiklio apklausąJS-03-RHF [forked] by sixthextinction. Čia vyksta gana daug, todėl verta sulėtinti tempą, kad pastebėtumėte, kur viskas baigėsi.
Išvestinės vertės – tarpinė suma, mokestis, bendra suma – apskaičiuojamos komponente naudojant „useWatch“ ir „useMemo“, nes jos priklauso nuo tiesioginių lauko verčių ir joms nėra kitos natūralios vietos. Naudotojo vardo, slaptažodžio, teigiamo atsiliepimo ir tobulinimo matomumo taisyklės JSX pateikiamos kaip įtrauktos sąlygos. Žingsnių praleidimo logika – peržiūros puslapis rodomas tik tada, kai iš viso >= 100 – yra įterpta į kintamąjį showSubmit ir pateikimo sąlygą atliekant 3 veiksmą. Pati navigacija yra tik „useState“ skaitiklis, kurį rankiniu būdu didiname. „React Query“ tvarko bandymus, kaupimą talpykloje ir negaliojimą. Forma tiesiog iškviečia mutation.mutate su patvirtintais duomenimis.
Niekas iš to nėra neteisingas, per se. Tai vis dar yra idiotiškas „React“, o komponentas yra gana našus dėl to, kaip RHF išskiria pakartotinius vaizdus. Bet jei perduotumėte tai žmogui, kuris to neparašė, ir paprašytumėte paaiškinti, kokiomis sąlygomis peržiūros puslapis rodomas, jis turėtų atsekti per showSubmit, 3 veiksmo pateikimo sąlygą ir naršymo mygtuko logiką – tris atskiras vietas – ir atkurti taisyklę, kuri galėjo būti nurodyta vienoje eilutėje. Forma veikia, taip, bet elgesys nėra tikrinamas kaip sistema. Tai turi būti įvykdyta psichiškai. Dar svarbiau, kad jį pakeisti reikia inžinieriaus įsitraukimo. Net nedidelis pakeitimas, pavyzdžiui, koregavimas, kai pasirodo peržiūros veiksmas, reiškia komponento redagavimą, patvirtinimo atnaujinimą, ištraukimo užklausos atidarymą, peržiūros laukimą ir vėl įdiegimą. 2 dalis: schema pagrįsta (SurveyJS) Dabar sukurkime tą patį srautą naudodami schemą. Montavimas npm įdiegti survey-core survey-react-ui @tanstack/react-query
survey-core MIT licencijuotas nuo platformos nepriklausomas vykdymo variklis, užtikrinantis SurveyJS formų atvaizdavimą – tai dalis, kuri mums čia rūpi. Ji paima JSON schemą, sukuria vidinį modelį iš jos ir tvarko viską, kas kitu atveju būtų jūsų „React“ komponente: matomumo išraiškų įvertinimas, išvestinių verčių skaičiavimas, puslapio būsenos valdymas, tikrinimo stebėjimas ir sprendimas, ką reiškia „užbaigta“, atsižvelgiant į tai, kurie puslapiai iš tikrųjų buvo rodomi.
survey-react-uiNaudotojo sąsaja / atvaizdavimo sluoksnis, jungiantis tą modelį su React. Iš esmės tai yra
Kartu jie suteikia visiškai funkcionalų kelių puslapių formos vykdymo laiką, neįrašant nė vienos valdymo srauto eilutės. Pats schemos formatas, kaip minėta anksčiau, yra tik JSON – nėra DSL ar nieko patentuoto. Galite jį įtraukti, importuoti iš failo, gauti iš API arba saugoti duomenų bazės stulpelyje ir hidratuoti vykdymo metu. Ta pati forma, kaip ir duomenys Čia yra ta pati forma, šį kartą išreikšta kaip JSON objektas. Schema apibrėžia viską: struktūrą, patvirtinimą, matomumo taisykles, išvestinius skaičiavimus, puslapio naršymą ir perduoda ją modeliui, kuris įvertina jį vykdymo metu. Štai kaip visa tai atrodo:
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, el. paštas }, ] }, { pavadinimas: "užsakymas", elementai: [ { tipas: "tekstas", pavadinimas: "kaina", įvesties tipas: "skaičius", numatytoji vertė: 0 }, { tipas: "tekstas", pavadinimas: "kiekis", įvesties tipas: "skaičius", numatytoji vertė: 1 }, { tipas: "išskleidžiamasis sąrašas",pavadinimas: "taxRate", numatytoji vertė: 0,1, pasirinkimai: [ { vertė: 0,05, tekstas: "5%" }, { vertė: 0,1, tekstas: "10%" }, { vertė: 0,15, tekstas: "15%" } ] }, { tipas: "išraiška", pavadinimas: "subtotal"}} išraiška: "subtotal" "išraiška", pavadinimas: "mokestis", išraiška: "{subtotal} {taxRate}" }, { tipas: "išraiška", pavadinimas: "iš viso", posakis: "{subtotal} + {tax}" } ] }, { pavadinimas: "sąskaita", elementai: [ { tipas: "radiogroup", pavadinimas: "hasAccount", pavadinimas: ", tipas": " "naudotojo vardas", näkyväIf: "{hasAccount} = 'Taip'", isRequired: true }, { type: "text", name: "password", inputType: "slaptažodis", matomasIf: "{hasAccount} = 'Yes'", isRequired: true, tikrintojai: [{ tipas: "tekstas", min.6 simbolių ilgis ] tipas: "vertinimas", pavadinimas: "pasitenkinimas", normaMin: 1, normaMax: 5 }, { tipas: "komentaras", pavadinimas: "teigiamas atsiliepimas", matomasJei: "{pasitenkinimas} >= 4" }, { tipas: "komentaras", pavadinimas: "patobulinimasAtsiliepimas", matomasJei: "{}patenkinimas ]2}žiūra", matomasIf: "{viso} >= 100", elementai: [] } ]};
Palyginkite tai su RHF versija.
„SuperRefine“ bloko, kuriam sąlygiškai reikėjo vartotojo vardo ir slaptažodžio, nebėra. näkyväIf: „{hasAccount} = 'Taip'“ kartu su „isRequired“: „true“ abu klausimus sprendžia kartu, pačiame lauke, kur tikitės jas rasti. „useWatch“ + „useMemo“ grandinė, apskaičiuojanti tarpinę sumą, mokesčius ir sumą, pakeičiama trimis išraiškos laukais, kurie nurodo vienas kitą pagal pavadinimą. Peržiūros puslapio sąlyga, kurią RHF versijoje buvo galima atkurti tik per showSubmit, 3 veiksmo pateikimo šaką. Galiausiai, naršymo mygtuko logika yra vienintelė puslapio objekto ypatybė, kurią galima pamatyti.
Ten ta pati logika. Tiesiog schema suteikia jai vietą gyventi ten, kur ji matoma atskirai, o ne pasklidusi visame komponente. Taip pat atminkite, kad schemoje naudojamas tipas: „išraiška“ tarpinei sumai, mokesčiams ir sumai. Išraiška yra tik skaitoma ir daugiausia naudojama apskaičiuotoms reikšmėms rodyti. SurveyJS taip pat palaiko tipą: „html“ statiniam turiniui, tačiau apskaičiuotoms reikšmėms išraiška yra tinkamas pasirinkimas. Dabar apie „React“ pusę. Atvaizdavimas ir pateikimas Labai paprasta. Prijunkite „onComplete“ prie savo API tokiu pačiu būdu – naudodami „useMutation“ arba „plain fetch“:
importuoti { useState, useEffect, useRef } iš "react";importuoti { useMutation } iš "@tanstack/react-query";importuoti { modelį } iš "survey-core";importuoti { Survey } iš "survey-react-ui";importuoti "survey-core/survey-core".
eksporto funkcija SurveyForm() { const [modelis] = useState(() => new Model(surveySchema));
const mutacija = useMutation({ mutationFn: async (duomenys) => { const res = laukti fetch("/api/orders", { metodas: "POST", antraštės: { "Content-Type": "application/json" }, kūnas: JSON.stringify(data), }); if (!res.ok) throw new Error("Nepavyko pateikti"); return res.json(); }, });
const mutacijaRef = useRef(mutacija); mutationRef.current = mutacija; useEffect(() => { const handler = (sender) => mutationRef.current.mutate(sender.data); model.onComplete.add(handler); return () => model.onComplete.remove(handler); }, [modelis]); // nuorodoje išvengiama tvarkyklės perregistravimo kiekvieną kartą (pakeičiant mutacijos objekto tapatybę)
grįžti (
<>
Žiūrėkite Pen SurveyJS-03-SurveyJS [forked] by sixthextinction.
onComplete suaktyvinamas, kai naudotojas pasiekia paskutinio matomo puslapio pabaigą. Taigi, jei bendra suma niekada neviršija 100 ir peržiūros puslapis praleidžiamas, jis vis tiek suaktyvinamas tinkamai, nes „SurveyJS“ įvertina matomumą prieš nuspręsdama, ką reiškia „paskutinis puslapis“. Tada sender.data pateikiami visi atsakymai kartu su apskaičiuotomis reikšmėmis (tarpinė suma, mokesčiai, bendra suma) kaip pirmos klasės laukai, todėl API naudingoji apkrova yra tokia pati kaip RHF versija, surinkta rankiniu būdu naudojant onSubmit. ThemutationRef šablonas yra tas pats, kurį pasieksite visur, kur reikia stabilios įvykių tvarkyklės, kurios vertė kinta kiekvieną kartą pateikiant – nieko konkretaus SurveyJS.
Komponente „React“ nebėra jokios verslo logikos. Nėra „useWatch“, nėra sąlyginio JSX, nėra žingsnių skaitiklio, nėra „useMemo“ grandinės, nėra „SuperRefine“. „React“ daro tai, ką iš tikrųjų moka: pateikia komponentą ir prijungia jį prie API skambučio. Kas pasitraukė iš reakcijos?
Susirūpinimas RHF kamino ApklausaJS Matomumas JSX filialai matomasJei Išvestinės vertės useWatch / useMemo išraiška Kryžminio lauko taisyklės SuperRefine Schemos sąlygos Navigacija žingsnio būsena Puslapis matomasJei Taisyklės vieta Paskirstytas failuose Centralizuota schemoje
„React“ lieka išdėstymas, stilius, pateikimo laidai ir programų integravimas, ty dalykai, kuriems „React“ iš tikrųjų yra sukurta. Visa kita perkelta į schemą, o kadangi schema yra tik JSON objektas, ją galima saugoti duomenų bazėje, jos versijas sukurti nepriklausomai nuo programos kodo arba redaguoti naudojant vidinius įrankius nereikalaujant diegimo. Produkto vadybininkas, kuriam reikia pakeisti peržiūros puslapį suaktyvinančią slenkstį, gali tai padaryti neliesdamas komponento. Tai reikšmingas veiklos skirtumas komandoms, kuriose formų elgsena dažnai keičiasi ir ne visada ją lemia inžinieriai. Kada naudoti kiekvieną metodą? Man tinka gera nykščio taisyklė: įsivaizduokite, kad visiškai ištrinsite formą. Ką tu prarastum?
Jei tai ekranai, norite komponentais pagrįstų formų. Jei tai verslo logika, pvz., slenksčiai, šakojimo taisyklės ir sąlyginiai reikalavimai, koduojantys tikrus sprendimus, jums reikia schemos variklio.
Panašiai, jei jūsų laukiantys pakeitimai daugiausia susiję su etiketėmis, laukais ir išdėstymu, RHF jums puikiai pasitarnaus. Jei jie susiję su sąlygomis, rezultatais ir taisyklėmis, kurias antradienio popietę gali tekti pakoreguoti jūsų operacijoms ar teisinei komandai nepateikus bilieto, schemos modelis su SurveyJS yra sąžiningesnis. Šie du metodai iš tikrųjų nekonkuruoja vienas su kitu. Jie sprendžia skirtingas problemų klases, o klaida, kurios verta vengti, yra abstrakcijos neatitikimas logikos svoriui – taisyklių sistema traktuojama kaip komponentas, nes tai yra pažįstamas įrankis, arba politikos variklis, nes forma išaugo iki trijų žingsnių ir įgavo sąlyginį lauką. Forma, kurią čia sukūrėme, yra sąmoningai šalia ribos, pakankamai sudėtinga, kad atskleistų skirtumą, bet ne tokia ekstremali, kad palyginimas atrodytų klaidingas. Dauguma realių formų, kurios jūsų kodų bazėje tapo nepatogios, tikriausiai yra netoli tos pačios ribos, ir paprastai kyla klausimas, ar kas nors įvardijo, kas jos iš tikrųjų yra. Naudokite React Hook Form + Zod, kai:
Formos yra orientuotos į CRUD; Logika yra sekli ir pagrįsta vartotojo sąsaja; Inžinieriams priklauso visas elgesys; Užpakalinė dalis išlieka tiesos šaltiniu.
Naudokite SurveyJS, kai:
Formos užkoduoja verslo sprendimus; Taisyklės vystosi nepriklausomai nuo vartotojo sąsajos; Logika turi būti matoma, tikrinama arba su versijomis; Ne inžinieriai daro įtaką elgesiui; Ta pati forma turi veikti keliose sąsajose.