Энэхүү нийтлэлийг SurveyJS ивээн тэтгэсэн болно Ихэнх React хөгжүүлэгчид үүнийг чангаар хэлэлцэхгүйгээр хуваалцдаг оюун санааны загвар байдаг. Энэ маягтууд нь үргэлж бүрэлдэхүүн хэсэг байх ёстой. Энэ нь стек гэсэн үг:

Орон нутгийн мужид зориулсан React Hook маягт (хамгийн бага дахин дүрслэл, эргономикийн талбарын бүртгэл, зайлшгүй харилцан үйлчлэл). Баталгаажуулалтын Zod (оролтын зөв, хилийн баталгаажуулалт, төрөл аюулгүй задлан шинжлэх). Backend-д зориулсан React Query: илгээх, дахин оролдох, кэш хийх, серверийн синк хийх гэх мэт.

Таны нэвтрэх дэлгэц, тохиргооны хуудас, CRUD загвар зэрэг ихэнх маягтын хувьд энэ нь үнэхээр сайн ажилладаг. Хэсэг бүр үүргээ гүйцэтгэдэг, тэд цэвэрхэн зохиодог бөгөөд та өөрийн бүтээгдэхүүнийг үнэхээр ялгаж буй програмынхаа хэсгүүдэд шилжиж болно. Гэхдээ хааяа нэг маягт нь өмнөх хариултаас хамаарах харагдах байдлын дүрэм, эсвэл гурван талбараар дамждаг үүсмэл утгууд зэрэг зүйлсийг хуримтлуулж эхэлдэг. Бүр алгасах эсвэл ажиллаж байгаа нийт дүнгээр харуулах ёстой бүх хуудсууд ч байж магадгүй. Та эхний нөхцөлийг useWatch болон шугаман салбараар зохицуулдаг бөгөөд энэ нь зүгээр. Дараа нь өөр. Дараа нь та Zod схем тань ердийн байдлаар илэрхийлэх боломжгүй хөндлөн талбарын дүрмийг кодлохын тулд superRefine-д хүрч байна. Дараа нь алхам навигаци нь бизнесийн логикийг алдаж эхэлдэг. Хэзээ нэгэн цагт та бүтээсэн зүйлээ хараад энэ маягт нь UI биш гэдгийг ойлгодог. Энэ бол шийдвэр гаргах үйл явц бөгөөд бүрэлдэхүүний мод нь яг таны хадгалсан газар юм. Энэ бол миний бодлоор React дахь хэлбэрийн сэтгэцийн загвар эвдэрсэн бөгөөд энэ нь үнэхээр хэний ч буруу биш юм. RHF + Zod стек нь зориулалтын дагуу маш сайн. Асуудлын гол нь бид үүнийг хийсвэрлэл нь асуудалтай таарч байгаа үед үргэлжлүүлэн ашиглах хандлагатай байдаг, учир нь өөр хувилбар нь маягтын талаар бүхэлд нь өөр арга барилыг шаарддаг. Энэ нийтлэл нь энэ хувилбарын тухай юм. Үүнийг харуулахын тулд бид яг ижил олон шатлалт маягтыг хоёр удаа бүтээх болно:

React Hook Form + Zod нь React Query-д холбогдсон байна. Маягтыг бүрэлдэхүүн мод гэхээсээ илүү энгийн JSON схем гэж үздэг SurveyJS-тэй.

Ижил шаардлага, ижил нөхцөлт логик, төгсгөлд нь ижил API дуудлага. Дараа нь бид яг юу нүүсэн, юу үлдсэнийг зураглаж, аль загварыг хэзээ, хэзээ ашиглахаа шийдэх бодит арга замыг гаргах болно. Бидний барьж буй хэлбэр:

Энэ маягт нь 4 үе шаттай урсгалыг ашиглана: Алхам 1: Дэлгэрэнгүй мэдээлэл

Нэр (шаардлагатай), Имэйл (шаардлагатай, хүчинтэй формат).

Алхам 2: Захиалга

Нэгж үнэ, Тоо хэмжээ, Татварын хувь хэмжээ, Гарсан: Дунд нийлбэр, Татвар, Нийт.

Алхам 3: Бүртгэл ба санал хүсэлт

Та данстай юу? (Тийм/Үгүй) Хэрэв тийм → хэрэглэгчийн нэр + нууц үг бол хоёулаа шаардлагатай. Хэрэв Үгүй → 1-р алхамд имэйлийг аль хэдийн цуглуулсан.

Сэтгэл ханамжийн үнэлгээ (1–5) Хэрэв ≥ 4 бол → “Чи юунд дуртай байсан бэ?” гэж асуу. Хэрэв ≤ 2 → “Бид юуг сайжруулах вэ?” гэж асуу.

Алхам 4: Хянах

Нийт >= 100 бол л харагдана Эцсийн мэдүүлэг.

Энэ бол хэт туйлшрал биш юм. Гэхдээ энэ нь архитектурын ялгааг ил гаргахад хангалттай. 1-р хэсэг: Бүрэлдэхүүн хэсэг (React Hook Form + Zod) Суурилуулалт npm install react-hook-form zod @hookform/resolvers @tanstack/react-query

Зод схем Зод схемээс эхэлцгээе, учир нь энэ нь ихэвчлэн маягтын хэлбэрийг тогтоодог. Эхний хоёр алхамд - хувийн мэдээлэл, захиалгын оролт - бүх зүйл энгийн: шаардлагатай мөрүүд, хамгийн бага тоо, тоолол. Сонирхолтой хэсэг нь нөхцөлт дүрмийг илэрхийлэхийг оролдох үед эхэлдэг.

"zod" -аас { z } импортлох;

экспортын const formSchema = z.object({ firstName: z.string().min(1, "Шаардлагатай"), имэйл: z.string().email("Хүчингүй имэйл"), үнэ: z.number().min(0), тоо хэмжээ: z.number().min(1), taxRate: z.number(), hasAccount(), username(),"z:"z z.string().optional(), нууц үг: z.string().optional(), сэтгэл ханамж: z.number().min(1).max(5), эерэгFeedback:z.string().optional(), improvementFeedback: z.string().optional(),}).superRefine((өгөгдөл, ctxda) ="s. (!data.username) { ctx.addIssue({код: "хэрэглэгчийн нэр", зам: ["хэрэглэгчийн нэр"], мессеж: "Шаардлагатай" }); } if (!data.password || data.password.length < 6) { ctx.addIssue({код: "хэрэглэгч", үг "M"sp:}"); }

if (data.satisfaction >= 4 && !data.positiveFeedback) { ctx.addIssue({код: "захиалгат", зам: ["эерэг санал хүсэлт"], мессеж: "Таалагдсан зүйлээ хуваалцана уу" }); }

хэрэв (өгөгдөл.сэтгэл ханамж <= 2 && !data.improvementFeedback) { ctx.addIssue ({ код: "захиалгат", зам:["improvementFeedback"], мессеж: "Юуг сайжруулах талаар бидэнд хэлнэ үү" }); }});

экспортын төрөл FormData = z.infer;

Zod-ийн төрлийн түвшний схем нь талбар чухал үед хамаарах дүрмийг бус объектын хэлбэрийг тодорхойлдог тул хэрэглэгчийн нэр болон нууц үг нь нөхцөлт шаардлагатай хэдий ч нэмэлт() хэлбэрээр бичигдсэн болохыг анхаарна уу. Нөхцөлт шаардлага нь дүрсийг баталгаажуулсны дараа ажилладаг бөгөөд бүрэн объект руу нэвтрэх боломжтой superRefine дотор байх ёстой. Энэ салалт нь алдаа биш юм; Энэ нь уг хэрэгсэлд зориулагдсан зүйл юм: superRefine нь схемийн бүтцэд өөрөө илэрхийлэх боломжгүй үед хөндлөн талбарын логик явдаг газар юм. Энд бас анхаарал татахуйц зүйл бол энэ схемд юуг илэрхийлэхгүй байгаа явдал юм. Энэ нь хуудасны тухай ойлголт, аль талбарууд аль цэг дээр харагдах тухай ойлголт, навигацийн тухай ойлголтгүй. Энэ бүхэн өөр газар амьдрах болно. Маягтын бүрэлдэхүүн хэсэг

"react-hook-form"-аас { useForm, useWatch } импортлох; "@hookform/resolvers/zod"-оос { zodResolver } импортлох; "@tanstack/react-query"-ээс { useMutation } импортлох; "react"-аас { useState, useMemo } импортлох; Import { formSchema}-аас FormSchema, төрөл "/;";

const STEPS = ["дэлгэрэнгүй", "захиалга", "акаунт", "хяналт"];

төрөл OrderPayload = FormData & { дэд нийлбэр: тоо; татвар: тоо; нийт: тоо };

экспортын функц RHFMultiStepForm() { const [алхам, setStep] = useState(0);

const мутаци = useMutation({ mutationFn: асинхронгуй (ачаалал: OrderPayload) => { const res = хүлээж авахыг хүлээж байна("/api/orders", { арга: "POST", гарчиг: { "Агуулгын төрөл": "application/json" }, их бие: JSON.stringify(ачаалал), }); хэрэв (!res.ok) шинэ алдаа гаргавал("Илгээж чадсангүй"); буцаах res.json(); }, });

const { бүртгэл, хяналт, handleSubmit, formState: { алдаа }, } = useForm({шийдвэрлэгч: zodResolver(formSchema), defaultValues: { үнэ: 0, тоо хэмжээ: 1, taxRate: 0.1, сэтгэл ханамж: 3, hasAccount: "Үгүй", },); const үнэ = useWatch({хяналт, нэр: "үнэ"}); const quantity = useWatch({хяналт, нэр: "тоо хэмжээ"}); const taxRate = useWatch({хяналт, нэр: "taxRate"}); const hasAccount = useWatch({хяналт, нэр: "hasAccount"}); const сэтгэл ханамж = useWatch({хяналт, нэр: "сэтгэл ханамж"}); const subtotal = useMemo(() => (үнэ ?? 0) * (тоо хэмжээ ?? 1), [үнэ, тоо хэмжээ]); const tax = useMemo(() => дэд нийлбэр * (taxRate ?? 0), [subnital, taxRate]); const нийт = useMemo(() => дэд нийлбэр + татвар, [дэд нийлбэр, татвар]); const onSubmit = (өгөгдөл: FormData) => mutation.mutate({ ...өгөгдөл, дэд нийлбэр, татвар, нийт }); const showSubmit = (алхам === 2 && нийт < 100) || (алхам === 3 && нийт >= 100)

буцаах (

{алхам === 0 && ( <> <оролт {...регистр("firstName")} орлуулагч="Нэр" /> <оролтын {...register("имэйл")} орлуулагч="И-мэйл" /> )}

{алхам === 1 && ( <> <оролтын төрөл="тоо" {...бүртгэл("үнэ", { valueAsNumber: үнэн })} /> <оролтын төрөл="тоо" {...бүртгэл("тоо хэмжээ", { valueAsNumber: үнэн })} /> <сонгох {...бүртгэх("taxRate", {утга}} <<А) утга = "0.05">5%

Дэд нийт: {subtotal}
Татвар: {tax}
Нийт: {нийт}
)}

{алхам === 2 && ( <> <сонгох {...register("hasAccount")}>

{hasAccount === "Тийм" && ( <> <оролт {...бүртгэл("хэрэглэгчийн нэр")} орлуулагч="Хэрэглэгчийн нэр" /> <оролт {...бүртгэл("нууц үг")} орлуулагч="Нууц үг" /> )}

<оролтын төрөл="тоо" {...регистр("сэтгэл ханамж", { valueAsNumber: үнэн })} />

{сэтгэл ханамж >= 4 && (