ეს სტატია დაფინანსებულია SurveyJS-ის მიერ არსებობს გონებრივი მოდელი, რომელსაც უმეტესობა React-ის დეველოპერები აზიარებენ მასზე ხმამაღლა განხილვის გარეშე. რომ ფორმები ყოველთვის უნდა იყოს კომპონენტები. ეს ნიშნავს დასტას, როგორიცაა:

React Hook Form ადგილობრივი მდგომარეობისთვის (მინიმალური ხელახალი რენდერები, ერგონომიული ველის რეგისტრაცია, იმპერატიული ურთიერთქმედება). Zod ვალიდაციისთვის (შეყვანის სისწორე, საზღვრების დადასტურება, ტიპის უსაფრთხო პარსინგი). უპასუხეთ შეკითხვას backend-ისთვის: წარდგენა, ხელახალი ცდები, ქეშირება, სერვერის სინქრონიზაცია და ა.შ.

და ფორმების უმრავლესობისთვის - თქვენი შესვლის ეკრანები, თქვენი პარამეტრების გვერდები, თქვენი CRUD მოდალები - ეს ძალიან კარგად მუშაობს. თითოეული ნაწილი თავის საქმეს აკეთებს, ისინი სუფთად ქმნიან და თქვენ შეგიძლიათ გადახვიდეთ თქვენი განაცხადის იმ ნაწილებზე, რომლებიც რეალურად განასხვავებენ თქვენს პროდუქტს. მაგრამ დროდადრო, ფორმა იწყებს ისეთი ნივთების დაგროვებას, როგორიცაა ხილვადობის წესები, რომლებიც დამოკიდებულია ადრინდელ პასუხებზე, ან მიღებული მნიშვნელობები, რომლებიც კასკადირდება სამ ველში. შესაძლოა, მთელი გვერდებიც კი, რომლებიც უნდა გამოტოვოთ ან ნაჩვენები იყოს გაშვებული ჯამიდან გამომდინარე. თქვენ ამუშავებთ პირველ პირობითს useWatch-ით და inline განშტოებით, რაც კარგია. მერე სხვა. შემდეგ თქვენ მიისწრაფვით superRefine-ისთვის, რათა დაშიფროთ ველური ჯვარედინი წესები, რომლებსაც თქვენი Zod სქემა ვერ გამოხატავს ნორმალურად. შემდეგ, ნაბიჯების ნავიგაცია იწყებს ბიზნეს ლოგიკის გაჟონვას. რაღაც მომენტში, თქვენ უყურებთ რა ააშენეთ და ხვდებით, რომ ფორმა ნამდვილად აღარ არის UI. ეს უფრო გადაწყვეტილების პროცესია და კომპონენტის ხე სწორედ იქაა, სადაც ის შეინახე. აქ ვფიქრობ, რომ React-ში ფორმების გონებრივი მოდელი იშლება და ეს ნამდვილად არავის ბრალი არ არის. RHF + Zod სტეკი შესანიშნავია იმაში, რისთვისაც შეიქმნა. საქმე იმაშია, რომ ჩვენ ვაგრძელებთ მის გამოყენებას იმ წერტილიდან, სადაც მისი აბსტრაქციები ემთხვევა პრობლემას, რადგან ალტერნატივა ფორმებზე სრულიად განსხვავებულ აზროვნებას მოითხოვს. ეს სტატია ეხება ამ ალტერნატივას. ამის საჩვენებლად, ჩვენ ორჯერ ავაშენებთ ზუსტად იგივე მრავალსაფეხურიან ფორმას:

React Hook Form + Zod-ით, რომელიც დაკავშირებულია React Query-ზე წარდგენისთვის, SurveyJS-ით, რომელიც ფორმას განიხილავს როგორც მონაცემს - მარტივი JSON სქემა - და არა კომპონენტის ხეს.

იგივე მოთხოვნები, იგივე პირობითი ლოგიკა, იგივე API გამოძახება ბოლოს. შემდეგ ჩვენ ზუსტად დავსახავთ რუკას, თუ რა გადავიდა და რა დარჩა, და გამოვყოფთ პრაქტიკულ გზას იმის გადასაწყვეტად, რომელი მოდელი და როდის უნდა გამოიყენოთ. ფორმა, რომელსაც ჩვენ ვაშენებთ:

ეს ფორმა გამოიყენებს 4-საფეხურიან ნაკადს: ნაბიჯი 1: დეტალები

სახელი (აუცილებელია), ელფოსტა (აუცილებელი, სწორი ფორმატი).

ნაბიჯი 2: შეკვეთა

ერთეულის ფასი, რაოდენობა, გადასახადის განაკვეთი, მიღებული: ქვეჯამური, გადასახადი, სულ.

ნაბიჯი 3: ანგარიში და გამოხმაურება

გაქვთ ანგარიში? (დიახ/არა) თუ დიახ → მომხმარებლის სახელი + პაროლი, ორივე აუცილებელია. თუ არა → ელფოსტა უკვე შეგროვებული ნაბიჯი 1.

კმაყოფილების რეიტინგი (1–5) თუ ≥ 4 → იკითხეთ "რა მოგეწონათ?" თუ ≤ 2 → იკითხეთ "რა შეგვიძლია გავაუმჯობესოთ?"

ნაბიჯი 4: განხილვა

გამოჩნდება მხოლოდ იმ შემთხვევაში, თუ სულ >= 100 საბოლოო წარდგენა.

ეს არ არის უკიდურესი. მაგრამ საკმარისია არქიტექტურული განსხვავებების გამოვლენა. ნაწილი 1: კომპონენტზე ორიენტირებული (React Hook Form + Zod) ინსტალაცია npm დააინსტალირე react-hook-form zod @hookform/resolvers @tanstack/react-query

ზოდის სქემა დავიწყოთ Zod-ის სქემით, რადგან, როგორც წესი, აქ ყალიბდება ფორმის ფორმა. პირველი ორი ნაბიჯისთვის - პერსონალური დეტალები და შეკვეთის შეყვანა - ყველაფერი მარტივია: საჭირო სტრიქონები, ნომრები მინიმუმებით და რიცხვი. საინტერესო ნაწილი იწყება, როცა ცდილობ პირობითი წესების გამოხატვას.

იმპორტი { z } "zod"-დან;

ექსპორტი const formSchema = z.object({ firstName: z.string().min(1, "საჭირო"), ელფოსტა: z.string().email("არასწორი ელფოსტა"), ფასი: z.number().min(0), რაოდენობა: z.number().min(1), taxRate: z.number(), hasAccount:(No"]esum. z.string().სურვილისამებრ(), პაროლი: z.string().სურვილისამებრ(), კმაყოფილება: z.number().min(1).max(5), დადებითი გამოხმაურება: z.string().სურვილისამებრ(), გაუმჯობესებაგამოხმაურება: z.string().სურვილისამებრ(),}).superRefine((data, {Afsda)=ha"{Afsda) =ha"= (!data.username) {ctx.addIssue({cod: "custom", path: ["username"], message: "Required" } } if (!data.password || data.password.length < 6) {ctx.addIssue({cod: "password"]"); }

if (data.satisfaction >= 4 && !data.positiveFeedback) { ctx.addIssue({ კოდი: "ჩვეულებრივი", ბილიკი: ["positiveFeedback"], შეტყობინება: "გთხოვთ გააზიაროთ ის, რაც მოგეწონათ" }); }

if (data.satisfaction <= 2 && !data.improvementFeedback) { ctx.addIssue({ კოდი: "საბაჟო", გზა:["improvementFeedback"], შეტყობინება: "გთხოვთ გვითხრათ რა გავაუმჯობესოთ" }); }});

ექსპორტის ტიპი FormData = z.infer;

გაითვალისწინეთ, რომ მომხმარებლის სახელი და პაროლი აკრეფილია როგორც არასავალდებულო() მიუხედავად იმისა, რომ ისინი პირობითად საჭიროა, რადგან Zod-ის ტიპის დონის სქემა აღწერს ობიექტის ფორმას და არა წესებს, რომლებიც არეგულირებს ველებს მნიშვნელობას. პირობითი მოთხოვნა უნდა იცხოვროს superRefine-ში, რომელიც მუშაობს ფორმის დამოწმების შემდეგ და აქვს წვდომა სრულ ობიექტზე. რომ განცალკევება არ არის ნაკლი; ეს არის მხოლოდ ის, რისთვისაც ინსტრუმენტია შექმნილი: superRefine არის ის ადგილი, სადაც ჯვარედინი ველის ლოგიკა მიდის, როდესაც მისი გამოხატვა შეუძლებელია თავად სქემის სტრუქტურაში. აქ ასევე აღსანიშნავია ის, რასაც ეს სქემა არ გამოხატავს. მას არ აქვს გვერდების კონცეფცია, არ აქვს კონცეფცია იმისა, თუ რომელი ველები რომელ წერტილში ჩანს და არც ნავიგაციის კონცეფცია. ეს ყველაფერი სხვაგან იცხოვრებს. ფორმის კომპონენტი

იმპორტი { useForm, useWatch } "react-hook-form"-დან;იმპორტი { zodResolver }-დან "@hookform/resolvers/zod";იმპორტი {useMutation }-დან "@tanstack/react-query";იმპორტი {useState, useMemo } "react"-დან;import type {formSchema"-დან;

const STEPS = ["დეტალები", "შეკვეთა", "ანგარიში", "მიმოხილვა"];

აკრიფეთ OrderPayload = FormData & { subtotal: ნომერი; გადასახადი: ნომერი; სულ: ნომერი };

ექსპორტის ფუნქცია RHFMultiStepForm() { const [ნაბიჯი, setStep] = useState(0);

const mutation = useMutation({ mutationFn: async (payload: OrderPayload) => { const res = ელოდება მიღებას("/api/orders", { მეთოდი: "POST", headers: { "Content-Type": "application/json" }, სხეული: JSON.stringify(payload), }); თუ (!res.ok) ჩააგდოს ახალი შეცდომა ("ვერ იქნა გაგზავნილი"); დაბრუნება res.json(); }, });

const { register, control, handleSubmit, formState: { errors }, } = useForm({ solver: zodResolver(formSchema), defaultValues: { ფასი: 0, რაოდენობა: 1, taxRate: 0.1, satisfaction: 3, hasAccount: "No}", const price = useWatch({ control, name: "price" }); const quantity = useWatch({ control, name: "quantity" }); const taxRate = useWatch({ control, name: "taxRate" }); const hasAccount = useWatch({ კონტროლი, სახელი: "hasAccount" }); const satisfaction = useWatch({ control, name: "satisfaction" }); const subtotal = useMemo(() => (ფასი ?? 0) * (რაოდენობა ?? 1), [ფასი, რაოდენობა]); const tax = useMemo(() => subtotal * (taxRate ?? 0), [subtotal, taxRate]); const total = useMemo(() => subtotal + tax, [subtotal, tax]); const onSubmit = (მონაცემები: FormData) => mutation.mutate({ ...data, subtotal, tax, total }); const showSubmit = (ნაბიჯი === 2 && სულ < 100) || (ნაბიჯი === 3 და სულ >= 100)

დაბრუნება (

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

{step === 1 && ( <>

ქვეჯამლი: {subtotal}
გადასახადი: {tax}
სულ: {total}
)}

{step === 2 && ( <>

{hasAccount === "დიახ" && ( <> )}

{satisfaction >= 4 && (