Այս հոդվածը հովանավորվում է 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

Զոդի սխեման Սկսենք Զոդի սխեմայից, քանի որ սովորաբար հենց այստեղ է ձևավորվում ձևի ձևը: Առաջին երկու քայլերի համար՝ անձնական տվյալներ և պատվերի մուտքագրում, ամեն ինչ պարզ է՝ պահանջվող տողեր, թվեր նվազագույններով և թվով: Հետաքրքիրը սկսվում է այն ժամանակ, երբ փորձում ես արտահայտել պայմանական կանոնները։

ներմուծել { z } «zod»-ից;

արտահանում const formSchema = z.object({ firstName: z.string().min(1, «Պահանջվում է»), էլ. z.string().ընտրովի(), գաղտնաբառ՝ z.string().ընտրովի(), բավարարվածություն՝ z.number().min(1).max(5), positiveFeedback՝ z.string().optional(), բարելավումՀետադարձ կապ՝ z.string().ընտրովի(),}).superRefine((տվյալներ, {Afsda)=ha" (!data.username) {ctx.addIssue({ code: "custom", path. }

if (data.satisfaction >= 4 && !data.positiveFeedback) {ctx.addIssue({ կոդը՝ «custom», ուղի՝ [«positiveFeedback»], հաղորդագրություն՝ «Խնդրում ենք կիսվել այն, ինչ ձեզ դուր է եկել» }); }

if (data.satisfaction <= 2 && !data.improvementFeedback) {ctx.addIssue({ կոդը՝ «custom», path:["improvementFeedback"], հաղորդագրություն. «Խնդրում եմ, ասեք մեզ, թե ինչ բարելավենք» }); }});

արտահանման տեսակը FormData = z.infer;

Ուշադրություն դարձրեք, որ օգտանունը և գաղտնաբառը մուտքագրվում են որպես ընտրովի(), չնայած դրանք պայմանականորեն պահանջվում են, քանի որ Zod-ի տիպի մակարդակի սխեման նկարագրում է օբյեկտի ձևը, այլ ոչ թե դաշտերի նշանակությունը կարգավորող կանոնները: Պայմանական պահանջը պետք է ապրի superRefine-ի ներսում, որն աշխատում է այն բանից հետո, երբ ձևը վավերացվում է և հասանելի է դառնում ամբողջական օբյեկտին: Այդ բաժանումը թերություն չէ. դա հենց այն է, ինչի համար նախատեսված է գործիքը. superRefine-ն այն տեղն է, որտեղ անցնում է խաչաձեւ դաշտային տրամաբանությունը, երբ այն չի կարող արտահայտվել հենց սխեմայի կառուցվածքում: Այստեղ հատկանշական է նաև այն, ինչ այս սխեման չի արտահայտում: Այն չունի էջերի հասկացություն, չկա հասկացություն, թե որ կետում որ դաշտերը տեսանելի են, և չկա նավիգացիա: Այդ ամենը կապրի մեկ այլ տեղ։ Ձևի բաղադրիչ

ներմուծում {useForm, useWatch } «react-hook-form»-ից;ներմուծում {zodResolver }-ից «@hookform/resolvers/zod»-ից;ներմուծում {useMutation }-ից «@tanstack/react-query»-ից;ներմուծում {useState, useMemo } «react»-ից;ներմուծում տիպը «FormDsche-ից», ներմուծում {formSchema-ից:

const STEPS = [«մանրամասներ», «պատվեր», «հաշիվ», «վերանայում»];

տեսակ OrderPayload = FormData & { subtotal: number; հարկ՝ համար; ընդհանուր՝ համարը };

արտահանման ֆունկցիա RHFMultiStepForm() {const [քայլ, setStep] = useState(0);

const mutation = useMutation({ mutationFn՝ async (վճարելի բեռ՝ OrderPayload) => { const res = await fetch("/api/orders", { մեթոդ՝ «ՓՈՍՏ», վերնագրեր՝ { "Content-Type": "application/json" }, մարմին՝ JSON.stringify(payload), }); եթե (!res.ok) նետել նոր Սխալ ("Չհաջողվեց ներկայացնել"); վերադարձնել res.json(); }, });

const { register, control, handleSubmit, formState: { errors }, } = useForm({լուծող: zodResolver(formSchema), defaultValues: {գին` 0, քանակ` 1, հարկային դրույքաչափ` 0.1, բավարարվածություն` 3, hasAccount: "No,}", const price = useWatch ({ control, name: "price" }); const quantity = useWatch({ control, name: "quantity" }); const taxRate = useWatch ({ control, name: "taxRate" }); const hasAccount = useWatch ({ control, name: "hasAccount" }); const satisfaction = useWatch({ control, name: "satisfaction" }); const subtotal = useMemo(() => (գինը ?? 0) * (քանակը ?? 1), [գինը, քանակությունը]); const tax = useMemo(() => subtotal * (taxRate ?? 0), [subtotal, taxRate]); const total = useMemo(() => ենթատոտալ + հարկ, [ենթատոտալ, հարկ]); const onSubmit = (տվյալներ՝ FormData) => mutation.mutate({ ...տվյալներ, ենթատոտալ, հարկ, ընդհանուր }); const showSubmit = (քայլ === 2 && ընդհանուր < 100) || (քայլ === 3 && ընդհանուր >= 100)

վերադարձ (

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

{step === 1 && ( <>

Ենթաընդհանուր` {subtotal}
Հարկ` {tax}
Ընդամենը` {total}
)}

{step === 2 && ( <>

{hasAccount === «Այո» && ( <> )}

{satisfaction >= 4 && (