Artikel ini ditaja oleh SurveyJS Terdapat model mental yang dikongsi oleh kebanyakan pembangun React tanpa membincangkannya dengan lantang. Borang itu sentiasa sepatutnya menjadi komponen. Ini bermakna timbunan seperti:
Borang Cangkuk Bertindak balas untuk keadaan setempat (pemarahan semula minimum, pendaftaran medan ergonomik, interaksi penting). Zod untuk pengesahan (ketepatan input, pengesahan sempadan, penghuraian selamat jenis). React Query untuk backend: penyerahan, cuba semula, caching, penyegerakan pelayan dan sebagainya.
Dan untuk sebahagian besar borang — skrin log masuk anda, halaman tetapan anda, mod CRUD anda — ini berfungsi dengan baik. Setiap bahagian melakukan tugasnya, mereka mengarang dengan bersih, dan anda boleh beralih ke bahagian aplikasi anda yang sebenarnya membezakan produk anda. Tetapi sekali-sekala, borang mula mengumpul perkara seperti peraturan keterlihatan yang bergantung pada jawapan terdahulu atau nilai terbitan yang mengalir melalui tiga medan. Mungkin juga keseluruhan halaman yang harus dilangkau atau ditunjukkan berdasarkan jumlah yang sedang berjalan. Anda mengendalikan bersyarat pertama dengan useWatch dan cawangan sebaris, yang tidak mengapa. Kemudian yang lain. Kemudian anda mendapatkan superRefine untuk mengekod peraturan merentas medan yang skema Zod anda tidak dapat dinyatakan dengan cara biasa. Kemudian, navigasi langkah mula membocorkan logik perniagaan. Pada satu ketika, anda melihat apa yang telah anda bina dan menyedari bahawa borang itu bukan UI lagi. Ia lebih kepada proses keputusan, dan pokok komponen adalah tempat anda menyimpannya. Di sinilah saya fikir model mental untuk borang dalam React rosak, dan ini sebenarnya bukan salah sesiapa. Timbunan RHF + Zod sangat baik untuk kegunaannya. Isunya ialah kita cenderung untuk terus menggunakannya melepasi titik di mana abstraksinya sepadan dengan masalah kerana alternatif itu memerlukan cara pemikiran yang berbeza tentang bentuk sepenuhnya. Artikel ini adalah mengenai alternatif itu. Untuk menunjukkan ini, kami akan membina borang berbilang langkah yang sama dua kali:
Dengan React Hook Form + Zod berwayar ke React Query untuk penyerahan, Dengan SurveyJS, yang menganggap borang sebagai data — skema JSON yang mudah — dan bukannya pokok komponen.
Keperluan yang sama, logik bersyarat yang sama, panggilan API yang sama pada penghujungnya. Kemudian kami akan memetakan dengan tepat apa yang telah dipindahkan dan apa yang kekal, dan menyediakan cara praktikal untuk menentukan model yang anda patut gunakan, dan bila. Borang yang kami bina:
Borang ini akan menggunakan aliran 4 langkah: Langkah 1: Butiran
Nama pertama (diperlukan), E-mel (diperlukan, format yang sah).
Langkah 2: Pesanan
harga seunit, Kuantiti, Kadar cukai, Diperoleh: Jumlah kecil, cukai, Jumlah.
Langkah 3: Akaun & Maklum Balas
Adakah anda mempunyai akaun? (Ya/Tidak) Jika Ya → nama pengguna + kata laluan, kedua-duanya diperlukan. Jika Tidak → e-mel sudah dikumpulkan dalam langkah 1.
Penilaian kepuasan (1–5) Jika ≥ 4 → tanya “Apa yang anda suka?” Jika ≤ 2 → tanya “Apakah yang boleh kami perbaiki?”
Langkah 4: Semakan
Hanya muncul jika jumlah >= 100 Penyerahan akhir.
Ini tidak melampau. Tetapi ia cukup untuk mendedahkan perbezaan seni bina. Bahagian 1: Didorong Komponen (Borang Cangkuk Reaksi + Zod) Pemasangan npm pasang react-hook-form zod @hookform/resolvers @tanstack/react-query
Skema Zod Mari kita mulakan dengan skema Zod, kerana biasanya di situlah bentuk bentuk terbentuk. Untuk dua langkah pertama — butiran peribadi dan input pesanan — semuanya adalah mudah: rentetan yang diperlukan, nombor dengan minimum dan enum. Bahagian yang menarik bermula apabila anda cuba menyatakan peraturan bersyarat.
import { z } daripada "zod";
eksport const formSchema = z.objek({Nama pertama: z.string().min(1, "Diperlukan"), e-mel: z.string().e-mel("E-mel tidak sah"), harga: z.number().min(0), kuantiti: z.number().min(1), Kadar cukai: z.number(), hasAccount:z.enum" kata laluan: z.string().optional(), kepuasan: z.number().min(1).max(5), positiveFeedback: z.string().optional(), improvementFeedback: z.string().optional(),}).superRefine((data, ctx) => { if (data.hasAccount === "Ya") { if (!data.username) { s. ["nama pengguna"], mesej: "Diperlukan" }); } jika (!data.password || data.password.length < 6) { ctx.addIssue({ code: "custom", path: ["password"], message: "Min 6 characters" } }
jika (data.satisfaction >= 4 && !data.positiveFeedback) { ctx.addIssue({ code: "custom", path: ["positiveFeedback"], message: "Sila kongsi perkara yang anda suka" }); }
if (data.satisfaction <= 2 && !data.improvementFeedback) { ctx.addIssue({ code: "custom", path:["ImprovementFeedback"], mesej: "Sila beritahu kami perkara yang perlu diperbaiki" }); }});
jenis eksport FormData = z.infer
Perhatikan bahawa nama pengguna dan kata laluan ditaip sebagai pilihan() walaupun ia diperlukan secara bersyarat kerana skema peringkat jenis Zod menerangkan bentuk objek, bukan peraturan yang mengawal apabila medan penting. Keperluan bersyarat perlu hidup di dalam superRefine, yang dijalankan selepas bentuk disahkan dan mempunyai akses kepada objek penuh. Perpisahan itu bukanlah satu kecacatan; ia hanya untuk alat ini: superRefine ialah tempat logik merentas medan pergi apabila ia tidak boleh dinyatakan dalam struktur skema itu sendiri. Perkara yang juga ketara di sini ialah perkara yang tidak dinyatakan oleh skema ini. Ia tidak mempunyai konsep halaman, tiada konsep medan yang boleh dilihat pada titik mana, dan tiada konsep navigasi. Semua itu akan tinggal di tempat lain. Komponen Borang
import { useForm, useWatch } daripada "react-hook-form";import { zodResolver } daripada "@hookform/resolvers/zod";import { useMutation } daripada "@tanstack/react-query";import { useState, useMemo } daripada "react";import { formSchema, taip FormData }"
const STEPS = ["perincian", "pesanan", "akaun", "semakan"];
taip OrderPayload = FormData & { subtotal: number; cukai: nombor; jumlah: bilangan };
fungsi eksport RHFMultiStepForm() { const [step, setStep] = useState(0);
mutasi const = useMutation({ mutationFn: async (muatan muatan: OrderPayload) => { const res = await fetch("/api/orders", { kaedah: "POST", pengepala: { "Content-Type": "application/json" }, badan: JSON.stringify(payload), }); jika (!res.ok) buang Ralat baharu("Gagal menyerahkan"); return res.json(); }, });
const { register, control, handleSubmit, formState: { errors }, } = useForm
pulangkan (
);}Lihat Pen SurveyJS-03-RHF [bercabang] oleh sixtheextinction. Terdapat banyak perkara yang berlaku di sini, dan ia patut diperlahankan untuk melihat di mana perkara itu berakhir.
Nilai terbitan — jumlah kecil, cukai, jumlah — dikira dalam komponen melalui useWatch dan useMemo kerana ia bergantung pada nilai medan langsung dan tiada tempat semula jadi lain untuknya. Peraturan keterlihatan untuk nama pengguna, kata laluan, positiveFeedback dan improvementFeedback hidup di JSX sebagai syarat sebaris. Logik melangkau langkah — halaman ulasan hanya muncul apabila jumlah >= 100 — dibenamkan ke dalam pembolehubah showSubmit dan syarat pemaparan pada langkah 3. Navigasi itu sendiri hanyalah kaunter useState yang kami tingkatkan secara manual. React Query mengendalikan percubaan semula, caching dan penolakan. Borang itu hanya memanggil mutation.mutate dengan data yang disahkan.
Semua ini tidak salah, per se. Ini masih idiomatik React, dan komponennya agak berprestasi terima kasih kepada cara pengasingan RHF menghasilkan semula. Tetapi jika anda menyerahkan ini kepada seseorang yang belum menulisnya dan meminta mereka menerangkan dalam keadaan apa halaman semakan itu muncul, mereka perlu mengesan melalui showSubmit, keadaan render langkah 3 dan logik butang nav — tiga tempat berasingan — untuk membina semula peraturan yang boleh dinyatakan dalam satu baris. Borang itu berfungsi, ya, tetapi tingkah laku itu tidak benar-benar boleh diperiksa sebagai satu sistem. Ia perlu dilaksanakan secara mental. Lebih penting lagi, mengubahnya memerlukan penglibatan kejuruteraan. Walaupun tweak kecil, seperti melaraskan apabila langkah semakan muncul, bermakna mengedit komponen, mengemas kini pengesahan, membuka permintaan tarik, menunggu semakan dan menggunakan semula. Bahagian 2: Dipacu Skema (SurveyJS) Sekarang mari kita bina aliran yang sama menggunakan skema. Pemasangan npm install survey-core survey-react-ui @tanstack/react-query
survey-core Enjin masa jalan bebas platform berlesen MIT yang menjanakan pemaparan borang SurveyJS — bahagian yang kami ambil berat di sini. Ia memerlukan skema JSON, membina model dalaman daripadanya dan mengendalikan semua yang mungkin wujud dalam komponen React anda: menilai ungkapan keterlihatan, mengira nilai terbitan, mengurus keadaan halaman, menjejaki pengesahan dan memutuskan maksud "lengkap" memandangkan halaman yang sebenarnya dipaparkan.
survey-react-ui Lapisan UI / pemaparan yang menghubungkan model tersebut kepada React. Ia pada asasnya ialah komponen
Bersama-sama, mereka memberi anda masa jalan bentuk berbilang halaman yang berfungsi sepenuhnya tanpa menulis satu baris aliran kawalan. Format skema itu sendiri, seperti yang dinyatakan sebelum ini, hanya JSON — tiada DSL atau apa-apa proprietari. Anda boleh menyelaraskannya, mengimportnya daripada fail, mengambilnya daripada API atau menyimpannya dalam lajur pangkalan data dan menghidratkannya semasa masa jalan. Borang Yang Sama, Seperti Data Berikut ialah bentuk yang sama, kali ini dinyatakan sebagai objek JSON. Skema mentakrifkan segala-galanya: struktur, pengesahan, peraturan keterlihatan, pengiraan terbitan, navigasi halaman — dan menyerahkannya kepada Model yang menilainya semasa masa jalan. Inilah rupanya sepenuhnya:
export const surveySchema = { title: "Order Flow", showProgressBar: "top", halaman: [ { name: "details", elements: [ { type: "text", name: "firstName", isRequired: true }, { type: "text", name: "emel", inputType: "email", isRequired: true", {:text: validator }] } ] }, { nama: "pesanan", elemen: [ { jenis: "teks", nama: "harga", inputType: "nombor", defaultValue: 0 }, { type: "text", nama: "kuantiti", inputType: "nombor", defaultValue: 1 }, { type: "dropdown",nama: "TaxRate", defaultValue: 0.1, pilihan: [ { value: 0.05, text: "5%" }, { value: 0.1, text: "10%" }, { value: 0.15, text: "15%" } ] }, { type: "expression", name: "quantity}"}: expression "ungkapan", nama: "cukai", ungkapan: "{subtotal} {taxRate}" }, { type: "expression", nama: "total", ungkapan: "{subtotal} + {tax}" } ] }, { name: "account", elemen: [ { user: "radiogroup", nama: "hasAccount", pilihan: "If],": "No type" "{hasAccount} = 'Yes'", isRequired: true }, { type: "text", name: "password", inputType: "password", visibleIf: "{hasAccount} = 'Yes'", isRequired: true, validators: [{ type: "text", minLength: 6, text: "Min 6 characters": {type}: "rate name" . ]};
Bandingkan ini dengan versi RHF untuk seketika.
Blok superRefine yang memerlukan nama pengguna dan kata laluan secara bersyarat telah hilang. visibleIf: "{hasAccount} = 'Yes'" digabungkan dengan isRequired: true mengendalikan kedua-dua kebimbangan bersama-sama, di medan itu sendiri, di mana anda menjangkakan untuk mencarinya. Rantaian useWatch + useMemo yang mengira subjumlah, cukai dan jumlah digantikan dengan tiga medan ungkapan yang merujuk satu sama lain mengikut nama. Keadaan halaman semakan, yang dalam versi RHF hanya boleh dibina semula dengan mengesan melalui showSubmit, cawangan render langkah 3. Dan akhirnya, logik butang nav ialah satu sifat visibleIf pada objek halaman.
Logik yang sama ada. Cuma skema itu memberikannya tempat tinggal di mana ia boleh dilihat secara berasingan, dan bukannya merebak ke seluruh komponen. Juga, ambil perhatian bahawa skema menggunakan jenis: 'ungkapan' untuk jumlah kecil, cukai dan jumlah. Ungkapan adalah baca sahaja dan digunakan terutamanya untuk memaparkan nilai yang dikira. SurveyJS juga menyokong jenis: 'html' untuk kandungan statik, tetapi untuk nilai yang dikira, ungkapan ialah pilihan yang tepat. Sekarang untuk bahagian React. Penyampaian Dan Penyerahan Sangat mudah. Wire onComplete ke API anda dengan cara yang sama — melalui useMutation atau pengambilan biasa:
import { useState, useEffect, useRef } daripada "react";import { useMutation } daripada "@tanstack/react-query";import { Model } daripada "survey-core";import { Survey } daripada "survey-react-ui";import "survey-core/survey-core.css";
fungsi eksport SurveyForm() { const [model] = useState(() => new Model(surveySchema));
mutasi const = useMutation({ mutationFn: async (data) => { const res = await fetch("/api/orders", { kaedah: "POST", pengepala: { "Content-Type": "application/json" }, badan: JSON.stringify(data), }); jika (!res.ok) buang Ralat baharu("Gagal menyerahkan"); return res.json(); }, });
const mutationRef = useRef(mutasi); mutationRef.current = mutasi; useEffect(() => { const pengendali = (pengirim) => mutationRef.current.mutate(sender.data);model.onComplete.add(handler); return () => model.onComplete.remove(handler); }, [model]); // ref mengelakkan pendaftaran semula pengendali setiap render (perubahan identiti objek mutasi)
kembali (
<>
Lihat Pen SurveyJS-03-SurveyJS [bercabang] oleh sixtheextinction.
onComplete menyala apabila pengguna mencapai penghujung halaman terakhir yang kelihatan. Jadi jika jumlah tidak pernah melepasi 100 dan halaman ulasan dilangkau, ia masih menyala dengan betul kerana SurveyJS menilai keterlihatan sebelum memutuskan maksud "halaman terakhir". Kemudian, sender.data mengandungi semua jawapan bersama-sama dengan nilai yang dikira (jumlah kecil, cukai, jumlah) sebagai medan kelas pertama, jadi muatan API adalah sama dengan versi RHF yang dipasang secara manual dalam onSubmit. Thecorak mutationRef adalah sama yang anda capai di mana-mana sahaja anda memerlukan pengendali acara yang stabil atas nilai yang berubah pada setiap pemaparan — tiada apa-apa yang khusus SurveyJS mengenainya.
Komponen React tidak lagi mengandungi sebarang logik perniagaan sama sekali. Tiada useWatch, tiada JSX bersyarat, tiada pembilang langkah, tiada rantai useMemo, tiada superRefine. React sedang melakukan perkara yang sebenarnya bagus: memaparkan komponen dan pendawaian ke panggilan API. Apa yang Keluar Daripada Reaksi?
Keprihatinan Timbunan RHF SurveyJS Keterlihatan cawangan JSX nampakJika Nilai terbitan gunakanTonton / gunakanMemo ungkapan Peraturan merentas padang superRefine Syarat skema Navigasi keadaan langkah Halaman kelihatanJika Lokasi peraturan Diedarkan ke seluruh fail Berpusat dalam skema
Perkara yang kekal dalam React ialah reka letak, penggayaan, pendawaian penyerahan dan penyepaduan apl, yang bermaksud, perkara yang sebenarnya direka untuk React. Segala-galanya dipindahkan ke dalam skema, dan kerana skema itu hanyalah objek JSON, ia boleh disimpan dalam pangkalan data, versi secara bebas daripada kod aplikasi anda atau diedit melalui perkakas dalaman tanpa memerlukan penggunaan. Pengurus produk yang perlu menukar ambang yang mencetuskan halaman semakan boleh melakukannya tanpa menyentuh komponen. Itulah perbezaan operasi yang bermakna untuk pasukan yang tingkah laku bentuk sering berubah dan tidak selalu didorong oleh jurutera. Bila Untuk Menggunakan Setiap Pendekatan? Berikut ialah peraturan praktikal yang sesuai untuk saya: bayangkan memadamkan borang sepenuhnya. Apa yang anda akan rugi?
Jika ia adalah skrin, anda mahukan borang dipacu komponen. Jika logik perniagaan, seperti ambang, peraturan percabangan dan keperluan bersyarat yang mengekod keputusan sebenar, anda mahukan enjin skema.
Begitu juga, jika perubahan yang akan datang kepada anda kebanyakannya mengenai label, medan dan reka letak, RHF akan memberi perkhidmatan yang baik kepada anda. Jika mereka mengenai syarat, hasil dan peraturan yang mungkin perlu diselaraskan oleh ops atau pasukan undang-undang anda pada petang Selasa tanpa memfailkan tiket, model skema dengan SurveyJS adalah lebih sesuai. Kedua-dua pendekatan ini sebenarnya tidak bersaing antara satu sama lain. Mereka menangani kelas masalah yang berbeza, dan kesilapan yang patut dielakkan ialah tidak memadankan abstraksi dengan berat logik — menganggap sistem peraturan seperti komponen kerana itulah alat yang biasa, atau mencapai enjin dasar kerana borang berkembang kepada tiga langkah dan memperoleh medan bersyarat. Bentuk yang kami bina di sini terletak berhampiran sempadan dengan sengaja, cukup kompleks untuk mendedahkan perbezaan tetapi tidak terlalu melampau sehingga perbandingannya terasa dicurangi. Kebanyakan bentuk sebenar yang menjadi sukar digunakan dalam pangkalan kod anda mungkin terletak berhampiran sempadan yang sama, dan persoalannya biasanya hanya sama ada sesiapa telah menamakan apa sebenarnya mereka. Gunakan React Hook Form + Zod apabila:
Borang adalah berorientasikan CRUD; Logik adalah cetek dan dipacu UI; Jurutera memiliki semua tingkah laku; Backend kekal sebagai sumber kebenaran.
Gunakan SurveyJS apabila:
Borang mengekod keputusan perniagaan; Peraturan berkembang secara bebas daripada UI; Logik mestilah boleh dilihat, boleh diaudit, atau versi; Bukan jurutera mempengaruhi tingkah laku; Borang yang sama mesti dijalankan merentasi berbilang bahagian hadapan.