Šo rakstu sponsorē SurveyJS Pastāv prāta modelis, ar kuru vairums React izstrādātāju dalās, to nekad skaļi neapspriežot. Formām vienmēr ir jābūt sastāvdaļām. Tas nozīmē tādu kaudzi kā:
React Hook Form vietējai valstij (minimāla atkārtota renderēšana, ergonomiska lauka reģistrācija, obligāta mijiedarbība). Zod validācijai (ievades pareizība, robežu validācija, tipa droša parsēšana). Reaģēt uz aizmugursistēmas vaicājumu: iesniegšana, atkārtojumi, saglabāšana kešatmiņā, servera sinhronizācija un tā tālāk.
Un lielākajai daļai formu — jūsu pieteikšanās ekrānos, iestatījumu lapās, CRUD modāļos — tas darbojas ļoti labi. Katrs gabals veic savu darbu, tie ir tīri komponēti, un jūs varat pāriet uz lietojumprogrammas daļām, kas faktiski atšķir jūsu produktu. Taču ik pa laikam veidlapa sāk uzkrāt tādas lietas kā redzamības kārtulas, kas ir atkarīgas no iepriekšējām atbildēm, vai atvasinātās vērtības, kas tiek kaskādes cauri trim laukiem. Varbūt pat veselas lapas, kuras vajadzētu izlaist vai parādīt, pamatojoties uz kopējo kopējo skaitu. Jūs apstrādājat pirmo nosacījumu, izmantojot useWatch un iekļauto atzaru, un tas ir labi. Tad vēl viens. Pēc tam jūs izmantojat superRefine, lai kodētu starplauku noteikumus, kurus jūsu Zod shēma nevar izteikt parastajā veidā. Pēc tam soļu navigācija sāk nopludināt biznesa loģiku. Kādā brīdī jūs skatāties uz to, ko esat izveidojis, un saprotat, ka veidlapa vairs nav lietotāja saskarne. Tas ir vairāk lēmumu pieņemšanas process, un komponentu koks ir tieši tajā vietā, kur jūs to glabājāt. Šeit es domāju, ka React formu mentālais modelis sabojājas, un tā patiesībā nav neviena vaina. RHF + Zod steks ir lielisks tam, kam tas bija paredzēts. Problēma ir tāda, ka mums ir tendence to izmantot pēc tam, kad tā abstrakcijas atbilst problēmai, jo alternatīva prasa pilnīgi citu domāšanas veidu par formām. Šis raksts ir par šo alternatīvu. Lai to parādītu, mēs divreiz izveidosim tieši tādu pašu daudzpakāpju formu:
Ar React Hook Form + Zod pievienošanu React Query iesniegšanai, Ar SurveyJS, kas veidlapu apstrādā kā datus — vienkāršu JSON shēmu —, nevis kā komponentu koku.
Tās pašas prasības, tā pati nosacījuma loģika, viens un tas pats API izsaukums beigās. Pēc tam mēs precīzi norādīsim, kas tika pārvietots un kas palika, un izstrādāsim praktisku veidu, kā izlemt, kuru modeli un kad izmantot. Veidlapa, ko veidojam:
Šajā veidlapā tiks izmantota 4 pakāpju plūsma: 1. darbība. Sīkāka informācija
Vārds (obligāts), E-pasts (obligāts, derīgs formāts).
2. darbība: pasūtiet
Vienības cena, Daudzums, Nodokļa likme, Atvasināts: Starpsumma, Nodoklis, Kopā.
3. darbība: konts un atsauksmes
Vai jums ir konts? (Jā/Nē) Ja Jā → lietotājvārds + parole, abi ir nepieciešami. Ja Nē → e-pasts jau ir savākts 1. darbībā.
Apmierinātības vērtējums (1–5) Ja ≥ 4 → jautājiet “Kas jums patika?” Ja ≤ 2 → jautājiet “Ko mēs varam uzlabot?”
4. darbība. Pārskatiet
Parādās tikai tad, ja kopā >= 100 Galīgā iesniegšana.
Tas nav ekstrēmi. Bet ar to pietiek, lai atklātu arhitektūras atšķirības. 1. daļa: ar komponentiem darbināms (reaģēšanas āķa forma + Zod) Uzstādīšana npm instalēt react-hook-form zod @hookform/resolvers @tanstack/react-query
Zod shēma Sāksim ar Zod shēmu, jo tur parasti tiek noteikta formas forma. Pirmajās divās darbībās — personas informācija un pasūtījuma ievade — viss ir vienkārši: obligātās virknes, cipari ar minimumu un enum. Interesantā daļa sākas, mēģinot izteikt nosacījumus.
importēt { z } no "zod";
export const formSchema = z.object({ vārds: z.string().min(1, "Obligāts"), e-pasts: z.string().email("Nederīgs e-pasts"), cena: z.number().min(0), daudzums: z.number().min(1), taxRate: z.number(), hasAccount: ["]enum", "z.esum". z.string().optional(), parole: z.string().optional(), apmierinātība: z.skaitlis().min(1).maks.(5), pozitīvaAtsauksmes: z.string().optional(), uzlabojumsAtsauksmes: z.string().optional(),}).superRefine((data, ctxdata) =.=.ha { ifA (ctx) =. (!data.lietotājs } }
if (data.satisfaction >= 4 && !data.positiveFeedback) { ctx.addIssue({ kods: "pielāgots", ceļš: ["pozitīvāAtsauksmes"], ziņojums: "Lūdzu, dalieties ar to, kas jums patika" }); }
if (data.satisfaction <= 2 && !data.improvementFeedback) { ctx.addIssue({ kods: "pielāgots", ceļš:["improvementFeedback"], ziņojums: "Lūdzu, pastāstiet mums, ko uzlabot" }); }});
eksporta veids FormData = z.infer
Ņemiet vērā, ka lietotājvārds un parole tiek ievadīti kā neobligāti (), lai gan tie ir nosacīti nepieciešami, jo Zod tipa līmeņa shēma apraksta objekta formu, nevis noteikumus, kas nosaka, kad lauki ir svarīgi. Nosacītajai prasībai ir jāatbilst superRefine, kas tiek izpildīta pēc formas apstiprināšanas un tai ir piekļuve visam objektam. Šī atdalīšana nav trūkums; tas ir tieši tas, kam rīks ir paredzēts: superRefine ir vieta, kur tiek izmantota starplauku loģika, kad to nevar izteikt pašā shēmas struktūrā. Šeit ir arī ievērojams tas, ko šī shēma neizsaka. Tajā nav jēdziena par lapām, nav jēdziena par to, kuri lauki ir redzami kādā vietā, un nav jēdziena par navigāciju. Tas viss dzīvos kaut kur citur. Veidlapas sastāvdaļa
importēt { useForm, useWatch } no "react-hook-form";importēt { zodResolver } no "@hookform/resolvers/zod";importēt { useMutation } no "@tanstack/react-query";importēt { useState, useMemo } no "react";importēt no: "formSchema";
const STEPS = ["detaļas", "pasūtījums", "konts", "pārskats"];
type OrderPayload = FormData & { starpsumma: numurs; nodoklis: numurs; kopā: skaits };
eksporta funkcija RHFMultiStepForm() { const [solis, setStep] = useState(0);
const mutācija = useMutation({ mutationFn: async (lietderīgā slodze: OrderPayload) => { const res = gaidīt fetch("/api/orders", { metode: "POST", galvenes: { "Content-Type": "application/json"}, pamatteksts: JSON.stringify(payload), }); if (!res.ok) throw new Error("Neizdevās iesniegt"); atgriezties res.json(); }, });
const { register, control, handleSubmit, formState: { errors }, } = useForm
return (
);}Skatiet pildspalvu aptaujuJS-03-RHF [forked] by sixthextinction. Šeit notiek diezgan daudz, un ir vērts piebremzēt, lai pamanītu, kur viss beidzās.
Atvasinātās vērtības — starpsumma, nodoklis, kopsumma — komponentā tiek aprēķinātas, izmantojot useWatch un useMemo, jo tās ir atkarīgas no reāllaika lauka vērtībām un tām nav citas dabiskas vietas. Redzamības noteikumi lietotājvārdam, parolei, pozitīvaiAtsauksmes un uzlabojumiAtsauksmes tiek rādītas JSX kā iekļauti nosacījumi. Soļu izlaišanas loģika — pārskatīšanas lapa tiek parādīta tikai tad, ja kopā ir >= 100 — ir iegulta mainīgajā showSubmit un renderēšanas nosacījumā 3. darbībā. Pati navigācija ir tikai useState skaitītājs, ko mēs manuāli palielinām. React Query apstrādā atkārtotus mēģinājumus, saglabāšanu kešatmiņā un anulēšanu. Veidlapa tikai izsauc mutation.mutate ar apstiprinātiem datiem.
Nekas no tā nav nepareizs pats par sevi. Tas joprojām ir idiotisks React, un komponents ir diezgan veiktspējīgs, pateicoties tam, kā RHF izolē atkārtotus renderējumus. Taču, ja jūs to nodotu kādam, kurš to nav uzrakstījis, un lūgtu paskaidrot, kādos apstākļos tiek parādīta pārskatīšanas lapa, viņam būtu jāizseko, izmantojot šovuSubmit, 3. darbības renderēšanas nosacījumu un navigācijas pogas loģiku — trīs atsevišķas vietas, lai rekonstruētu noteikumu, kas varētu būt norādīts vienā rindā. Veidlapa darbojas, jā, bet uzvedība nav īsti pārbaudāma kā sistēma. Tas ir jāizpilda garīgi. Vēl svarīgāk ir tas, ka tā mainīšanai ir nepieciešama inženieru iesaiste. Pat neliela korekcija, piemēram, pielāgošana, kad parādās pārskatīšanas darbība, nozīmē komponenta rediģēšanu, validācijas atjaunināšanu, izvilkšanas pieprasījuma atvēršanu, pārskatīšanas gaidīšanu un atkārtotu izvietošanu. 2. daļa: ar shēmu vadīts (SurveyJS) Tagad izveidosim to pašu plūsmu, izmantojot shēmu. Uzstādīšana npm instalējiet survey-core survey-react-ui @tanstack/react-query
survey-core MIT licencēts no platformas neatkarīgs izpildlaika dzinējs, kas nodrošina SurveyJS veidlapu renderēšanu — šī daļa, kas mums šeit rūp. Tam tiek izmantota JSON shēma, no tās tiek izveidots iekšējais modelis un tiek apstrādāts viss, kas citādi atrastos jūsu React komponentā: redzamības izteiksmju novērtēšana, atvasināto vērtību aprēķināšana, lapas stāvokļa pārvaldība, izsekošanas validācija un izlemšana, ko nozīmē “pabeigts”, ņemot vērā, kuras lapas faktiski tika rādītas.
survey-react-ui UI/renderēšanas slānis, kas savieno šo modeli ar React. Būtībā tas ir komponents
Kopā tie nodrošina pilnībā funkcionālu, vairāku lappušu veidlapas izpildlaiku, nerakstot nevienu vadības plūsmas rindiņu. Pats shēmas formāts, kā minēts iepriekš, ir tikai JSON — bez DSL vai cita patentēta. Varat to iekļaut, importēt no faila, ienest no API vai saglabāt datu bāzes kolonnā un hidratēt izpildes laikā. Tāda pati forma, kā dati Šeit ir tā pati forma, kas šoreiz izteikta kā JSON objekts. Shēma nosaka visu: struktūru, validāciju, redzamības kārtulas, atvasinātus aprēķinus, lapas navigāciju — un nodod to modelim, kas to novērtē izpildes laikā. Lūk, kā tas izskatās pilnībā:
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, e-pasts }, ] }, { nosaukums: "pasūtījums", elementi: [ { tips: "teksts", nosaukums: "cena", ievades veids: "skaitlis", noklusējuma vērtība: 0 }, { tips: "teksts", nosaukums: "daudzums", ievades veids: "skaitlis", noklusējuma vērtība: 1 }, { veids: "nolaižamā izvēlne",nosaukums: "taxRate", noklusējumaVērtība: 0,1, izvēles: [ {vērtība: 0,05, teksts: "5%" }, { vērtība: 0,1, teksts: "10%" }, { vērtība: 0,15, teksts: "15%" } ] }, { veids: "izteiksme", nosaukums: "subtotal"}, izteiksme: "subtotal"} "izteiksme", nosaukums: "nodoklis", izteiksme: "{subtotal} {taxRate}" }, { tips: "izteiksme", nosaukums: "kopā", izteiksme: "{subtotal} + {tax}" } } }, { nosaukums: "konts", elementi: [ { tips: "radiogroup", nosaukums: "hasAccount", nosaukums: "Nē", teksts ": ":" "lietotājvārds", nähtavIf: "{hasAccount} = 'Jā'", isRequired: true }, { type: "text", name: "password", inputType: "password", nähtavIf: "{hasAccount} = 'Yes'", isRequired: true, validators: [{ type: "text ", min 6 , text }th, minLengs } tips: "vērtējums", nosaukums: "apmierinātība", rateMin: 1, rateMax: 5 }, { tips: "komentārs", nosaukums: "pozitīvsAtsauksmes", redzamsIf: "{apmierinātība} >= 4" }, { veids: "komentārs", nosaukums: "uzlabojumsAtsauksmes", redzamsIf: "{}nosaukums ]atsveiciens"}", redzamsIf: "{kopā} >= 100", elementi: [] } ]};
Uz brīdi salīdziniet to ar RHF versiju.
SuperRefine bloks, kas nosacīti prasīja lietotājvārdu un paroli, ir pazudis. näkyväIf: "{hasAccount} = "Jā"" kopā ar isRequired: true apstrādā abas problēmas kopā pašā laukā, kur jūs varētu tās atrast. Ķēde useWatch + useMemo, kas aprēķināja starpsummu, nodokli un kopsummu, tiek aizstāta ar trim izteiksmes laukiem, kas atsaucas viens uz otru pēc nosaukuma. Pārskatīšanas lapas nosacījums, kas RHF versijā bija rekonstruējams, tikai izsekojot caur showSubmit, 3. darbības renderēšanas zaru. Un visbeidzot, navigācijas pogas loģika ir viens redzamsIf rekvizīts lapas objektā.
Tur ir tāda pati loģika. Vienkārši shēma nodrošina tai dzīvesvietu, kur tā ir redzama atsevišķi, nevis izkliedēta pa komponentu. Ņemiet vērā arī to, ka shēmā tiek izmantots veids: “izteiksme” starpsummai, nodoklim un kopsummai. Izteiksme ir tikai lasāma, un to galvenokārt izmanto, lai parādītu aprēķinātās vērtības. SurveyJS atbalsta arī veidu: "html" statiskam saturam, bet aprēķinātām vērtībām izteiksme ir pareizā izvēle. Tagad par React pusi. Renderēšana un iesniegšana Ļoti vienkārši. Savienojiet onComplete savā API tādā pašā veidā — izmantojot useMutation vai plain fetch:
importēt { useState, useEffect, useRef } no "react";importēt { useMutation } no "@tanstack/react-query";importēt { modeli } no "survey-core";importēt { Survey } no "survey-react-ui";importēt "survey-core/survey-core".
eksporta funkcija SurveyForm() { const [modelis] = useState(() => new Model(surveySchema));
const mutācija = useMutation({ mutācijaFn: async (dati) => { const res = gaidīt fetch("/api/orders", { metode: "POST", galvenes: { "Content-Type": "application/json"}, pamatteksts: JSON.stringify(data), }); if (!res.ok) throw new Error("Neizdevās iesniegt"); atgriezties res.json(); }, });
const mutācijaRef = useRef(mutācija); mutationRef.current = mutācija; useEffect(() => { const handler = (sūtītājs) => mutācijaRef.current.mutate(sender.data); model.onComplete.add(handler); return () => model.onComplete.remove(handler); }, [modelis]); // ref izvairās no apdarinātāja pārreģistrācijas katrā renderēšanā (mutācijas objekta identitātes izmaiņas)
atgriezties (
<>
Skatiet Pen SurveyJS-03-SurveyJS [forked] by sixthextinction.
onComplete tiek aktivizēts, kad lietotājs sasniedz pēdējās redzamās lapas beigas. Tātad, ja kopējais skaits nekad nepārsniedz 100 un pārskata lapa tiek izlaista, tā joprojām tiek aktivizēta pareizi, jo SurveyJS novērtē redzamību, pirms izlemj, ko nozīmē “pēdējā lapa”. Pēc tam failā sender.data ir ietvertas visas atbildes kopā ar aprēķinātajām vērtībām (starpsumma, nodoklis, kopsumma) kā pirmās klases lauki, tāpēc API lietderīgā slodze ir identiska RHF versijai, kas manuāli apkopota pakalpojumā onSubmit. ThemutationRef modelis ir tas pats, ko jūs varētu sasniegt visur, kur jums ir nepieciešams stabils notikumu apdarinātājs ar vērtību, kas mainās katrā renderēšanas reizē — nekas nav raksturīgs SurveyJS.
Komponents React vairs nesatur nekādu biznesa loģiku. Nav useWatch, nav nosacījuma JSX, nav soļu skaitītāja, nav useMemo ķēdes, nav superRefine. React dara to, ko tas patiesībā prot: renderē komponentu un savieno to ar API zvanu. Kas pārcēlās no reakcijas?
Bažas RHF kaudze AptaujaJS Redzamība JSX filiāles redzamsJa Atvasinātās vērtības useWatch / useMemo izteiksme Cross-field noteikumi SuperRefine Shēmas nosacījumi Navigācija soļa stāvoklis Lapa redzamaJa Noteikumu atrašanās vieta Izplatīts pa failiem Centralizēta shēmā
React paliek izkārtojums, stils, iesniegšanas vadi un lietotņu integrācija, proti, lietas, kurām React ir faktiski paredzētas. Viss pārējais ir pārvietots shēmā, un, tā kā shēma ir tikai JSON objekts, to var glabāt datu bāzē, izveidot tās versijas neatkarīgi no lietojumprogrammas koda vai rediģēt, izmantojot iekšējos rīkus, neprasot izvietošanu. Produktu pārvaldnieks, kuram jāmaina slieksnis, kas aktivizē pārskatīšanas lapu, var to izdarīt, nepieskaroties komponentam. Tā ir nozīmīga darbības atšķirība komandām, kurās formas uzvedība bieži mainās un ne vienmēr to nosaka inženieri. Kad izmantot katru pieeju? Šis ir labs īkšķis, kas man noder: iedomājieties, ka veidlapu izdzēšat pilnībā. Ko tu zaudētu?
Ja tie ir ekrāni, jums ir vajadzīgas komponentu vadītas veidlapas. Ja tā ir biznesa loģika, piemēram, sliekšņi, sazarošanas noteikumi un nosacījuma prasības, kas kodē reālus lēmumus, jums ir nepieciešams shēmas dzinējs.
Tāpat, ja gaidāmās izmaiņas galvenokārt attiecas uz etiķetēm, laukiem un izkārtojumu, RHF jums noderēs lieliski. Ja runa ir par nosacījumiem, rezultātiem un noteikumiem, kas jūsu personālam vai juridiskajai komandai varētu būt jāpielāgo otrdienas pēcpusdienā, neiesniedzot biļeti, shēmas modelis ar SurveyJS ir vispiemērotākais. Šīs divas pieejas īsti nekonkurē viena ar otru. Tie attiecas uz dažādām problēmu klasēm, un kļūda, no kuras ir vērts izvairīties, ir abstrakcijas neatbilstība loģikas smagumam — noteikumu sistēma tiek uzskatīta par komponentu, jo tas ir pazīstams rīks, vai tiek izmantots politikas dzinējs, jo veidlapa pieauga līdz trim soļiem un ieguva nosacījuma lauku. Veidlapa, ko mēs šeit izveidojām, atrodas apzināti tuvu robežai, pietiekami sarežģīta, lai atklātu atšķirību, bet ne tik ekstrēma, lai salīdzinājums liktos viltots. Lielākā daļa reālo formu, kas jūsu kodu bāzē ir kļuvušas smagnējas, iespējams, atrodas netālu no tās pašas robežas, un parasti jautājums ir tikai par to, vai kāds ir nosaucis, kas tās patiesībā ir. Izmantojiet React Hook Form + Zod, ja:
Veidlapas ir orientētas uz CRUD; Loģika ir sekla un balstīta uz lietotāja interfeisu; Inženieriem pieder visa uzvedība; Aizmugure joprojām ir patiesības avots.
Izmantojiet SurveyJS, ja:
Veidlapas iekodē biznesa lēmumus; Noteikumi attīstās neatkarīgi no lietotāja interfeisa; Loģikai jābūt redzamai, pārbaudāmai vai versijai; Neinženieri ietekmē uzvedību; Vienai un tai pašai veidlapai ir jādarbojas vairākās saskarnēs.