Kirish
Juda ko‘p kiritish maydonlariga ega bo‘lgan ko‘p bosqichli formani mustaqil ravishda qo‘llab-quvvatlash qiyin, vaqtinchalik saqlash va boshlang‘ich qiymatlarni sozlash kabi funksiyalar qo‘shilganda esa u juda tez murakkablashadi. Shuning uchun formalar bilan ishlashda samarali “strategiya” kerak bo‘ladi. Ushbu maqolada TypeScript va React muhitida react-hook-form (RHF), Zod va Jotatidan foydalanib, murakkab formalarni strategik boshqarish usulini umumlashtiraman.
Murakkab formalar qachon buzila boshlaydi?
Forma kattalashgani sari quyidagi muammolar yuzaga kela boshlaydi.
1. Komponentlar haddan tashqari kattalashadi.
- Kiritish maydonlari, validatsiya mantig‘i, shartli renderlash va xatolarni qayta ishlash mantig‘i ko‘paygani sari formani boshqarish uchun mas’ul komponent eksponensial ravishda kattalashadi.
2. Qiymatlar yo‘qoladi yoki keraksiz ma’lumotlar yaratiladi.
- Shartli maydonlar mavjud bo‘lganda, joriy shartlarga mos kelmaydigan maydonlarning qiymatlari yo‘qolishi yoki keraksiz ravishda saqlanishi mumkin.
3. Vaqt nuqtalari soni oshgani sari tuzilma tezda murakkablashadi.
- Vaqtinchalik saqlash qo‘llab-quvvatlanganda yoki bir nechta shartli maydonlar mavjud bo‘lganda, qiymatlar qachon saqlanishi yoki o‘zgartirilishiga qarab turli holatlarni qayta ishlash kerakligi sababli kod tezda murakkablashadi.
Boshqaruv strategiyasini sozlash
Yuqoridagi muammolarni alohida-alohida hal qilish o‘rniga, ularni samarali boshqaruv tamoyillari asosida hal qilmoqchi bo‘ldim. Men ishlab chiqqan strategiyalar quyidagilardan iborat.
1. Takrorlanuvchi mantiqni minimallashtirish orqali umumiy kodni soddalashtirish
2. Shartlar o‘zgarganda, keraksiz shartli maydonlarning qiymatlarini tiklash.
3. Javob mantig‘ini shart qiymatlari va vaqt nuqtalari asosida tuzish.
4. Har bir bosqichda kiritish qiymatlarini tekshirish, so‘ng yakuniy ma’lumotlarni saqlashda to‘liq validatsiyani yana bir marta bajarish.
Strategiyani batafsil ko‘rib chiqish
(1) Umumiy turlarni aniqlash
// schemas/common.ts
import { z } from "zod";
export const MemberType = z.enum(["personal", "employee", "freelancer", "business"]);
export type MemberType = z.infer<typeof MemberType>;
export const basicProfileSchema = z.object({
nickname: z.string().min(2, "닉네임은 2자 이상이어야 합니다.").max(20),
...
});
(2) Har bir bosqich kodini ajratish va RHF'ning extend() hamda Zod'ning discriminatedUnion funksiyalaridan foydalanish
// schemas/steps/job.schema.ts
import { z } from "zod";
import { MemberType } from "../common";
const baseJob = z.object({
memberType: MemberType,
...
});
const employeeJob = baseJob.extend({
memberType: z.literal("employee"),
...
});
const freelancerJob = baseJob.extend({
memberType: z.literal("freelancer"),
...
});
const businessJob = baseJob.extend({
memberType: z.literal("business"),
...
});
const personalJob = baseJob.extend({
memberType: z.literal("personal"),
...
});
export const jobStepSchema = z.discriminatedUnion("memberType", [
personalJob,
employeeJob,
freelancerJob,
businessJob,
]);
export type JobStepForm = z.infer<typeof jobStepSchema>;
(3) Ularni to‘liq modelga birlashtirish
// schemas/profile.schema.ts
import { z } from "zod";
import { basicProfileSchema } from "./common";
import { jobStepSchema } from "./steps/job.schema";
export const profileSchema = z.object({
profile: basicProfileSchema,
job: jobStepSchema,
...
});
export type ProfileForm = z.infer<typeof profileSchema>;
Shu bosqichda kerak bo‘lganda qiymatlarni Zod'ning refine() va superRefine() funksiyalari yordamida tekshirish mumkin, Jotati esa joriy bosqich va vaqtinchalik qiymatlarni saqlash uchun ishlatiladi.
Komponentni amalga oshirish misoli
Komponent quyidagicha amalga oshiriladi. Agar vaqtinchalik qiymatlar mavjud bo‘lsa, ular dastlabki sozlashda ishlatiladi. Vaqtinchalik saqlash hamda joriy/tugallangan bosqichni saqlash joriy bosqich formasi yuborilganda bajariladi.
// steps/Job.tsx
import React, { useEffect } from "react";
import { useAtom } from "jotai";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { draftAtom, completedStepsAtom, currentStepAtom } from "../state/profileWizard.atoms";
import { mergeDraft } from "../state/mergeDraft";
import { jobStepSchema, type JobStepForm } from "../schemas/steps/Job.schema";
export function Job() {
const [draft, setDraft] = useAtom(draftAtom); // 임시 저장값 atom
const [completedSteps, setCompletedSteps] = useAtom(completedStepsAtom); // 완료된 단계를 저장하는 atom
const [currentStep, setCurrentStep] = useAtom(currentStepAtom); // 현재 단계를 나타내는 atom
const form = useForm<JobStepForm>({
resolver: zodResolver(jobStepSchema),
defaultValues: {
...
},
});
const memberType = form.watch("memberType");
const handleSubmit = (values) => {
// 1) 현재 스텝 값을 draft에 저장
setDraft(mergeDraft(draft, { job: values }));
// 2) 스텝 완료 처리
const nextCompleted = new Set(completedSteps);
nextCompleted.add(4); setCompletedSteps(nextCompleted);
// 3) 다음 스텝 이동
setCurrentStep((currentStep + 1) as any);
}
// 스텝 진입 시 임시 저장값으로 초기화
useEffect(() => {
if (!!draft?.job) {
form.reset(draft.job);
}
}, []);
// 조건 변경 시 불필요한 조건부 필드의 값 초기화 (관리 전략 2)
useEffect(() => {
const base = form.getValues();
if (memberType === "employee") {
form.reset({
memberType,
role: base.role,
...
} as JobStepForm);
}
if (memberType === "freelancer") {
form.reset({
memberType,
role: base.role,
...
} as JobStepForm);
}
if (memberType === "business") {
form.reset({
memberType,
role: base.role,
...
} as JobStepForm);
}
if (memberType === "personal") {
form.reset({
memberType,
role: base.role,
...
} as JobStepForm);
}
}, [memberType]);
return (
...
)
};
Xulosa
Yuqoridagi qisqa kod misoli orqali murakkab formalarni qayta ishlash strategiyasini qanday ishlab chiqish va qo‘llashni ko‘rib chiqdik. Avval asosiy tamoyillar va strategiyalarni belgilash orqali formalarni nafaqat samaraliroq boshqarish, balki ishlab chiqish va qo‘llab-quvvatlash vaqtini sezilarli darajada qisqartirish ham mumkin bo‘ldi. Ushbu tajriba menga yuqori sifatli kod tegishli siyosatlar asosida ham yaratilishi mumkinligini o‘rgatdi.
May