Ky artikull është i sponsorizuar nga SurveyJS Ekziston një model mendor që shumica e zhvilluesve të React ndajnë pa e diskutuar atë me zë të lartë. Që format supozohen gjithmonë të jenë përbërës. Kjo do të thotë një pirg si:
React Hook Form për gjendjen lokale (rindërrim minimal, regjistrim ergonomik i fushës, ndërveprim imperativ). Zod për vërtetim (korrektësia e hyrjes, vlefshmëria e kufijve, analizimi i sigurt për tipin). Reagon Query për backend: dorëzim, riprovime, caching, sinkronizimi i serverit, e kështu me radhë.
Dhe për shumicën dërrmuese të formave - ekranet tuaja të hyrjes, faqet e cilësimeve, modalet tuaja CRUD - kjo funksionon vërtet mirë. Secila pjesë bën punën e saj, ato kompozojnë pastër, dhe ju mund të kaloni në pjesët e aplikacionit tuaj që në fakt e dallojnë produktin tuaj. Por herë pas here, një formular fillon të grumbullojë gjëra të tilla si rregullat e dukshmërisë që varen nga përgjigjet e mëparshme, ose vlerat e nxjerra që kalojnë nëpër tre fusha. Ndoshta edhe faqe të tëra që duhet të anashkalohen ose të shfaqen bazuar në një total të ekzekutuar. Ju trajtoni kushtëzimin e parë me një useWatch dhe një degë inline, gjë që është në rregull. Pastaj një tjetër. Më pas, po kërkoni superRefine për të koduar rregullat ndërfushash që skema juaj Zod nuk mund t'i shprehë në mënyrën normale. Pastaj, navigimi i hapave fillon të rrjedhë nga logjika e biznesit. Në një moment, ju shikoni atë që keni ndërtuar dhe kuptoni se forma nuk është më në të vërtetë UI. Është më shumë një proces vendimmarrjeje dhe pema përbërëse është pikërisht aty ku ju ka ndodhur ta ruani atë. Këtu mendoj se modeli mendor për format në React prishet, dhe në të vërtetë nuk është faji i askujt. Rafti RHF + Zod është i shkëlqyer në atë për të cilin është krijuar. Çështja është se ne priremi ta përdorim atë përtej pikës ku abstraksionet e tij përputhen me problemin, sepse alternativa kërkon një mënyrë krejtësisht të ndryshme të të menduarit për format. Ky artikull ka të bëjë me atë alternativë. Për ta treguar këtë, ne do të ndërtojmë saktësisht të njëjtën formë me shumë hapa dy herë:
Me React Hook Form + Zod të lidhur me React Query për dorëzim, Me SurveyJS, i cili trajton një formë si të dhëna - një skemë e thjeshtë JSON - në vend të një pemë përbërëse.
Të njëjtat kërkesa, e njëjta logjikë e kushtëzuar, e njëjta thirrje API në fund. Më pas do të përshkruajmë saktësisht se çfarë ka lëvizur dhe çfarë ka mbetur, dhe do të parashtrojmë një mënyrë praktike për të vendosur se cilin model duhet të përdorni dhe kur. Formulari që po ndërtojmë:
Ky formular do të përdorë një rrjedhë me 4 hapa: Hapi 1: Detajet
Emri (kërkohet), Email (i detyrueshëm, format i vlefshëm).
Hapi 2: Porosit
Çmimi për njësi, Sasia, Shkalla e tatimit, Rrjedh: Nëntotali, Taksa, Gjithsej.
Hapi 3: Llogaria dhe komentet
A keni një llogari? (Po/Jo) Nëse po → emri i përdoruesit + fjalëkalimi, të dyja kërkohen. Nëse Jo → email i mbledhur tashmë në hapin 1.
Vlerësimi i kënaqësisë (1–5) Nëse ≥ 4 → pyesni "Çfarë ju pëlqeu?" Nëse ≤ 2 → pyesni "Çfarë mund të përmirësojmë?"
Hapi 4: Rishikoni
Shfaqet vetëm nëse gjithsej >= 100 Dorëzimi përfundimtar.
Kjo nuk është ekstreme. Por është e mjaftueshme për të ekspozuar dallimet arkitekturore. Pjesa 1: E drejtuar nga komponentët (React Hook Form + Zod) Instalimi npm instaloni react-hook-form zod @hookform/resolvers @tanstack/react-query
Skema e Zodit Le të fillojmë me skemën Zod, sepse zakonisht aty vendoset forma e formës. Për dy hapat e parë - detajet personale dhe hyrjet e porosive - gjithçka është e thjeshtë: vargjet e kërkuara, numrat me minimume dhe një numër. Pjesa interesante fillon kur përpiqeni të shprehni rregullat e kushtëzuara.
importoni { z } nga "zod";
export const formSchema = z.object({ firstName: z.string().min(1, "Required"), email: z.string().email("Email i pavlefshëm"), çmimi: z.number().min(0), sasia: z.number().min(1), norma e taksave: z.number(), has Account:(Nr"]Yesum. z.string().optional(), fjalëkalimi: z.string().optional(), kënaqësia: z.number().min(1).max(5), feedback pozitiv: z.string().optional(), përmirësimFeedback: z.string().optional(),}).superRefine((data, {Afsda) =Y=" (!data.username) { ctx.addIssue({ code: "custom", shteg: ["username"], message: "Required" } } if (!data.password || data.password.length < 6) { ctx.addIssue({ code: "password", shtegu]"); }
if (data.satisfaction >= 4 && !data.positiveFeedback) { ctx.addIssue({ kodi: "custom", rruga: ["positiveFeedback"], mesazhi: "Ju lutemi ndajeni atë që ju pëlqeu" }); }
if (data.kënaqësi <= 2 && !data.improvementFeedback) { ctx.addIssue({ kodi: "custom", shteg:["improvementFeedback"], mesazhi: "Ju lutemi na tregoni se çfarë të përmirësojmë" }); }});
lloji i eksportit FormData = z.infer
Vini re se emri i përdoruesit dhe fjalëkalimi janë shtypur si opsionale() edhe pse ato kërkohen me kusht, sepse skema e nivelit të tipit të Zod përshkruan formën e objektit, jo rregullat që rregullojnë kur fushat kanë rëndësi. Kërkesa e kushtëzuar duhet të jetojë brenda superRefine, e cila funksionon pasi forma është vërtetuar dhe ka akses në objektin e plotë. Kjo ndarje nuk është një e metë; është pikërisht ajo për të cilën mjeti është krijuar: superRefine është vendi ku shkon logjika ndërfushe kur ajo nuk mund të shprehet në vetë strukturën e skemës. Ajo që është gjithashtu e dukshme këtu është ajo që kjo skemë nuk shpreh. Nuk ka koncept të faqeve, asnjë koncept se cilat fusha janë të dukshme në cilën pikë dhe nuk ka koncept të navigimit. E gjithë kjo do të jetojë diku tjetër. Komponenti i Formës
import {useForm, useWatch } nga "react-hook-form";import { zodResolver } nga "@hookform/resolvers/zod";import {useMutation } nga "@tanstack/react-query";import {useState, useMemo } nga "react";importo lloji "FormSche";
const STEPS = ["detaje", "porosi", "llogari", "rishikim"];
shkruani OrderPayload = FormData & { nëntotali: numri; taksa: numri; totali: numri };
funksioni i eksportit RHFMultiStepForm() { const [hapi, setStep] = useState(0);
const mutation = useMutation({ mutationFn: asinkron (ngarkesa: OrderPayload) => { const res = prit fetch("/api/orders", { metoda: "POST", headers: { "Content-Type": "application/json" }, trupi: JSON.stringify(payload), }); nëse (!res.ok) hedh gabim të ri ("Dështoi të dorëzohet"); ktheje res.json(); }, });
const { register, control, handleSubmit, formState: { errors }, } = useForm
ktheje (
);}Shih Pen SurveyJS-03-RHF [forked] nga sixthextinction. Ka shumë gjëra që ndodhin këtu dhe ia vlen të ngadalësoni për të vënë re se ku përfunduan gjërat.
Vlerat e përftuara - nëntotali, taksa, totali - llogariten në komponent nëpërmjet useWatch dhe useMemo sepse ato varen nga vlerat e fushës së drejtpërdrejtë dhe nuk ka vend tjetër natyror për to. Rregullat e dukshmërisë për emrin e përdoruesit, fjalëkalimin, feedback pozitiv dhe përmirësimin e Feedback jetojnë në JSX si kushte inline. Logjika e kapërcimit të hapave - faqja e rishikimit shfaqet vetëm kur gjithsej >= 100 - është ngulitur në variablin showSubmit dhe kushti i paraqitjes në hapin 3. Vetë navigimi është thjesht një numërues i përdorimit të shtetit që ne po e shtojmë manualisht. React Query trajton përsëritjet, ruajtjen në memorie dhe anulimin. Forma thjesht thërret mutation.mutate me të dhëna të vërtetuara.
Asnjë nga këto nuk është e gabuar, në vetvete. Ky është ende idiomatik React, dhe komponenti është mjaft performues falë mënyrës sesi izolon RHF ri-përpunon. Por nëse do t'ia dorëzonit këtë dikujt që nuk e kishte shkruar dhe i kërkonit të shpjegonte se në çfarë kushtesh shfaqet faqja e rishikimit, ata do të duhet të gjurmojnë përmes showSubmit, kushtit të paraqitjes së hapit 3 dhe logjikës së butonit navigues - tre vende të veçanta - për të rindërtuar një rregull që mund të ishte deklaruar në një rresht. Forma funksionon, po, por sjellja nuk është vërtet e inspektueshme si sistem. Duhet të ekzekutohet mendërisht. Më e rëndësishmja, ndryshimi i tij kërkon përfshirje inxhinierike. Edhe një rregullim i vogël, si rregullimi kur shfaqet hapi i rishikimit, do të thotë redaktimi i komponentit, përditësimi i vlefshmërisë, hapja e një kërkese për tërheqje, pritje për rishikim dhe vendosje përsëri. Pjesa 2: E drejtuar nga skema (SurveyJS) Tani le të ndërtojmë të njëjtën rrjedhë duke përdorur një skemë. Instalimi npm instaloni survey-core survey-react-ui @tanstack/react-query
Survey-core Motori i ekzekutimit i pavarur nga platforma i licencuar nga MIT që fuqizon paraqitjen e formularit të SurveyJS - pjesa që na intereson këtu. Ai merr një skemë JSON, ndërton një model të brendshëm prej saj dhe trajton gjithçka që përndryshe do të jetonte në komponentin tuaj React: vlerësimin e shprehjeve të dukshmërisë, llogaritjen e vlerave të derivuara, menaxhimin e gjendjes së faqes, përcjelljen e vlefshmërisë dhe vendosjen se çfarë do të thotë "e plotë" duke pasur parasysh se cilat faqe u shfaqën në të vërtetë.
Survey-react-uiShtesa e ndërfaqes së përdoruesit / rendering që lidh atë model me React. Është në thelb një komponent
Së bashku, ato ju japin një kohë ekzekutimi plotësisht funksionale, me shumë faqe, pa shkruar një linjë të vetme të rrjedhës së kontrollit. Vetë formati i skemës është, siç u tha më parë, vetëm një JSON - pa DSL ose ndonjë gjë të pronarit. Mund ta futni, ta importoni nga një skedar, ta merrni nga një API ose ta ruani në një kolonë bazë të dhënash dhe ta hidratoni në kohën e ekzekutimit. E njëjta formë, si të dhënat Këtu është e njëjta formë, këtë herë e shprehur si një objekt JSON. Skema përcakton gjithçka: strukturën, vlefshmërinë, rregullat e dukshmërisë, llogaritjet e prejardhura, navigimin e faqeve - dhe ia dorëzon një Modeli që e vlerëson atë në kohën e ekzekutimit. Ja si duket e plotë:
export const surveySchema = {titulli: "Rrjedha e porosive", showProgressBar: "lart", faqet: [ { emri: "detajet", elementet: [ { type: "tekst", emri: "firstName", ështëKërkohet: e vërtetë }, { type: "text", emri: "email", inputType: "email", është e nevojshme: "email" }] } ] }, { emri: "porosi", elementet: [ { lloji: "tekst", emri: "çmimi", lloji i hyrjes: "numri", vlera e paracaktuar: 0 }, { lloji: "tekst", emri: "sasia", lloji i hyrjes: "numri", vlera e paracaktuar: 1 }, { lloji: "me zbritje",emri: "taxRate", vlera e paracaktuar: 0.1, zgjedhje: [ { vlera: 0.05, teksti: "5%" }, {vlera: 0.1, teksti: "10%" }, {vlera: 0.15, teksti: "15%" } ] }, { lloji: "shprehje", emri: "{}, lloji nëntotal" "shprehje", emri: "tax", shprehja: "{nëntotal} {taxRate}" }, { type: "shprehje", emri: "total", shprehje: "{nëntotal} + {tax}" } ] }, {emri: "llogari", elementet: [ { type: "radiogrup", emri: "hasAccount]" "hasAccount", "Jo: "Typi "t",} "username", visualIf: "{hasAccount} = 'Po'", është e nevojshme: e vërtetë }, { type: "text", emri: "password", inputType: "password", visualIf: "{hasAccount} = 'Po'", është e nevojshme: e vërtetë, vleftësuesit: [{ type: "text", min. 6]" lloji: "vlerësimi", emri: "kënaqësia", normaMin: 1, normaMax: 5 }, { lloji: "koment", emri: "përgjigje pozitive", vizualeIf: "{kënaqësi} >= 4" }, { lloji: "koment", emri: "përmirësimFeedback", dukshmeIf: "{kënaqësi""}} i dukshëmNëse: "{total} >= 100", elementet: [] } ]};
Krahasoni këtë me versionin RHF për një moment.
Blloku superRefine që kërkonte me kusht emrin e përdoruesit dhe fjalëkalimin është zhdukur. visualIf: "{hasAccount} = 'Po'" e kombinuar me isRequired: true trajton të dyja shqetësimet së bashku, në vetë fushën, ku do të prisni t'i gjenit. Zinxhiri useWatch + useMemo që llogarit nëntotalin, tatimin dhe totalin zëvendësohet nga tre fusha shprehjeje që i referohen njëra-tjetrës me emër. Kushti i faqes së rishikimit, i cili në versionin RHF ishte i rindërtueshëm vetëm duke gjurmuar përmes showSubmit, dega e paraqitjes së hapit 3. Dhe së fundi, logjika e butonit navig është një veti e vetme e dukshmeIf në objektin e faqes.
E njëjta logjikë është atje. Është thjesht se skema i jep asaj një vend për të jetuar ku është e dukshme në izolim, në vend që të përhapet në të gjithë komponentin. Gjithashtu, vini re se skema përdor llojin: 'shprehje' për nëntotalin, tatimin dhe totalin. Shprehja është vetëm për lexim dhe përdoret kryesisht për të shfaqur vlerat e llogaritura. SurveyJS gjithashtu mbështet llojin: 'html' për përmbajtje statike, por për vlerat e llogaritura, shprehja është zgjidhja e duhur. Tani për anën e React. Rendering Dhe Dorëzimi Shumë e thjeshtë. Lidheni në Complete në API-në tuaj në të njëjtën mënyrë — nëpërmjet përdorimit të Mutacionit ose të marrjes së thjeshtë:
import { useState, useEffect, useRef } nga "react";import { useMutation } nga "@tanstack/react-query";importo { Model } nga "survey-core";importo { Survey } nga "survey-react-ui";importo "survey-core/csurvey-core".
funksioni i eksportit SurveyForm() { const [model] = useState(() => new Model(surveySchema));
const mutation = useMutation({ mutationFn: asinkron (të dhëna) => { const res = prit fetch("/api/orders", { metoda: "POST", headers: { "Content-Type": "application/json" }, trupi: JSON.stringify(të dhëna), }); nëse (!res.ok) hedh gabim të ri ("Dështoi të dorëzohet"); ktheje res.json(); }, });
const mutationRef = useRef(mutation); mutacionRef.rrymë = mutacion; useEffect(() => { const handler = (dërguesi) => mutationRef.current.mutate(sender.data); model.onComplete.add(handler); return () => model.onComplete.remove(handler); }, [model]); // ref shmang ri-regjistrimin e mbajtësit të çdo interpretimi (ndryshon identiteti i objektit të mutacionit)
kthehu (
<>
Shih Pen SurveyJS-03-SurveyJS [forked] nga sixthextinction.
onComplete ndizet kur përdoruesi arrin në fund të faqes së fundit të dukshme. Pra, nëse totali nuk kalon kurrë 100 dhe faqja e rishikimit anashkalohet, ajo përsëri ndizet saktë sepse SurveyJS vlerëson dukshmërinë përpara se të vendosë se çfarë do të thotë "faqja e fundit". Më pas, sender.data përmban të gjitha përgjigjet së bashku me vlerat e llogaritura (nëntotali, taksa, totali) si fusha të klasit të parë, kështu që ngarkesa e API-së është identike me atë që versioni RHF grumbulloi manualisht në onSubmit. TëModeli mutationRef është i njëjti që do të arrinit kudo që keni nevojë për një mbajtës të qëndrueshëm të ngjarjeve mbi një vlerë që ndryshon në çdo paraqitje - asgjë specifike për SurveyJS.
Komponenti React nuk përmban më asnjë logjikë biznesi. Nuk ka useWatch, asnjë JSX të kushtëzuar, asnjë numërues hapash, asnjë zinxhir useMemo, asnjë superRefine. React po bën atë në të cilën është në të vërtetë i mirë: paraqitjen e një komponenti dhe lidhjen e tij me një thirrje API. Çfarë Moved Out Of React?
shqetësim Stack RHF SurveyJS Dukshmëria degët JSX të dukshmeNëse Vlerat e prejardhura përdorniWatch / useMemo shprehje Rregullat ndërfushe superpërso Kushtet e skemës Navigimi gjendja e hapit Faqja e dukshmeNëse Vendndodhja e rregullit Shpërndarë nëpër skedarë E centralizuar në skemë
Ajo që mbetet në React është faqosja, stili, instalimet elektrike për paraqitjen dhe integrimi i aplikacioneve, që do të thotë, gjërat për të cilat React është krijuar në të vërtetë. Çdo gjë tjetër u zhvendos në skemë dhe për shkak se skema është thjesht një objekt JSON, ajo mund të ruhet në një bazë të dhënash, të versionohet në mënyrë të pavarur nga kodi i aplikacionit tuaj, ose të modifikohet përmes veglave të brendshme pa kërkuar një vendosje. Një menaxher produkti që duhet të ndryshojë pragun që aktivizon faqen e rishikimit mund ta bëjë këtë pa prekur komponentin. Ky është një ndryshim domethënës operacional për ekipet ku sjellja e formës evoluon shpesh dhe nuk drejtohet gjithmonë nga inxhinierët. Kur të përdoret çdo qasje? Këtu është një rregull i mirë i përgjithshëm që funksionon për mua: imagjinoni ta fshini plotësisht formularin. Çfarë do të humbisje?
Nëse janë ekrane, ju dëshironi forma të drejtuara nga komponentët. Nëse është logjika e biznesit, si pragjet, rregullat e degëzimit dhe kërkesat e kushtëzuara që kodojnë vendime reale, ju dëshironi një motor skemash.
Në mënyrë të ngjashme, nëse ndryshimet që vijnë në rrugën tuaj kanë të bëjnë kryesisht me etiketat, fushat dhe paraqitjen, RHF do t'ju shërbejë mirë. Nëse ato kanë të bëjnë me kushte, rezultate dhe rregulla që mund t'i duhet t'i rregullojë operacionet ose ekipi juaj ligjor të martën pasdite pa paraqitur një biletë, modeli i skemës me SurveyJS është më i sinqerti. Këto dy qasje nuk janë realisht në konkurrencë me njëra-tjetrën. Ato trajtojnë klasa të ndryshme problemesh dhe gabimi që ia vlen të shmanget është mospërputhja e abstraksionit me peshën e logjikës - trajtimi i një sistemi rregullash si një komponent sepse ky është mjeti i njohur, ose arritja e një motori politikash sepse një formë u rrit në tre hapa dhe fitoi një fushë të kushtëzuar. Forma që ndërtuam këtu qëndron afër kufirit qëllimisht, mjaft komplekse për të ekspozuar ndryshimin, por jo aq ekstrem sa krahasimi të duket i manipuluar. Shumica e formave reale që janë bërë të pafuqishme në bazën tuaj të kodeve ndoshta ndodhen pranë të njëjtit kufi, dhe pyetja zakonisht është nëse dikush ka emërtuar se çfarë janë në të vërtetë. Përdorni React Hook Form + Zod kur:
Formularët janë të orientuar nga CRUD; Logjika është e cekët dhe e drejtuar nga UI; Inxhinierët zotërojnë të gjitha sjelljet; Backend mbetet burimi i së vërtetës.
Përdorni SurveyJS kur:
Formularët kodojnë vendimet e biznesit; Rregullat zhvillohen në mënyrë të pavarur nga UI; Logjika duhet të jetë e dukshme, e auditueshme ose e versionuar; Jo-inxhinierët ndikojnë në sjelljen; E njëjta formë duhet të shtrihet në disa fronte.