Šį 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({ sprendiklis: zodResolver(formSchema), defaultValues: { kaina: 0, kiekis: 1, taxRate: 0.1, pasitenkinimas: 3, hasAccount: "Ne", }, }); const kaina = useWatch({ valdiklis, pavadinimas: "kaina" }); const kiekis = useWatch({ valdiklis, pavadinimas: "kiekis" }); const taxRate = useWatch({ valdiklis, pavadinimas: "taxRate" }); const hasAccount = useWatch({ valdiklis, pavadinimas: "hasAccount" }); const satisfaction = useWatch({ valdiklis, pavadinimas: "pasitenkinimas" }); const tarpinė suma = useMemo(() => (kaina ?? 0) * (kiekis ?? 1), [kaina, kiekis]); const tax = useMemo(() => tarpinė suma * (taxRate ?? 0), [tarpinė suma, taxRate]); const total = useMemo(() => tarpinė suma + mokestis, [tarpinė suma, mokestis]); const onSubmit = (duomenys: FormData) => mutation.mutate({ ...duomenys, tarpinė suma, mokestis, visa }); const showPateikti = (žingsnis === 2 && iš viso < 100) || (žingsnis === 3 ir iš viso >= 100)

return (

{step === 0 && ( <> )}

{žingsnis === 1 && ( <>

Tarp suma: {subtotal}
Mokestis: {tax}
Iš viso: {total}
)}

{step === 2 && ( <>

{hasAccount === "Taip" && ( <> )}

{pasitenkinimas >= 4 && (