Bu makale SurveyJS tarafından desteklenmektedir Çoğu React geliştiricisinin yüksek sesle tartışmadan paylaştığı zihinsel bir model var. Bu formların her zaman bileşenler olması gerekiyordu. Bu şuna benzer bir yığın anlamına gelir:
Yerel durum için React Hook Formu (minimum yeniden işleme, ergonomik alan kaydı, zorunlu etkileşim). Doğrulama için Zod (giriş doğruluğu, sınır doğrulama, tür açısından güvenli ayrıştırma). Arka uç için React Query: gönderme, yeniden denemeler, önbelleğe alma, sunucu senkronizasyonu vb.
Ve formların büyük çoğunluğu için (giriş ekranlarınız, ayar sayfalarınız, CRUD modelleriniz) bu gerçekten işe yarıyor. Her parça işini yapar, temiz bir şekilde oluşur ve uygulamanızın ürününüzü gerçekten farklı kılan kısımlarına geçebilirsiniz. Ancak arada bir, bir form daha önceki yanıtlara bağlı olan görünürlük kuralları veya üç alandan geçen türetilmiş değerler gibi şeyleri biriktirmeye başlar. Belki de toplam sayıya göre atlanması veya gösterilmesi gereken sayfaların tamamı bile olabilir. İlk koşulluyu bir useWatch ve satır içi dalla halledersiniz, bu da sorun değil. Sonra bir tane daha. Daha sonra Zod şemanızın normal şekilde ifade edemediği alanlar arası kuralları kodlamak için superRefine'a ulaşıyorsunuz. Ardından adım navigasyonu iş mantığını sızdırmaya başlar. Bir noktada, oluşturduğunuz şeye bakıyorsunuz ve formun artık gerçekten kullanıcı arayüzü olmadığını fark ediyorsunuz. Bu daha çok bir karar sürecidir ve bileşen ağacı tam da onu sakladığınız yerdir. React'taki formlara ilişkin zihinsel modelin burada çöktüğünü düşünüyorum ve bu aslında kimsenin hatası değil. RHF + Zod yığını, tasarlandığı amaç açısından mükemmeldir. Sorun şu ki, soyutlamaları sorunla eşleşinceye kadar onu kullanmaya devam etme eğilimindeyiz çünkü alternatif, formlar hakkında tamamen farklı bir düşünme tarzı gerektiriyor. Bu makale bu alternatif hakkındadır. Bunu göstermek için aynı çok adımlı formu iki kez oluşturacağız:
React Hook Form + Zod'un gönderim için React Query'ye bağlanmasıyla, Bir formu bileşen ağacı yerine veri (basit bir JSON şeması) olarak ele alan SurveyJS ile.
Aynı gereksinimler, aynı koşullu mantık, sonunda aynı API çağrısı. Daha sonra tam olarak neyin taşındığını ve neyin kaldığını haritalandıracağız ve hangi modeli ne zaman kullanmanız gerektiğine karar vermenin pratik bir yolunu ortaya koyacağız. Oluşturduğumuz form:
Bu formda 4 adımlı bir akış kullanılacaktır: 1. Adım: Ayrıntılar
Ad (gerekli), E-posta (gerekli, geçerli format).
Adım 2: Sipariş Verin
Birim fiyatı, miktar, Vergi oranı, Türetilmiş: Ara toplam, vergi, Toplam.
3. Adım: Hesap ve Geri Bildirim
Hesabınız var mı? (Evet/Hayır) Evet ise → kullanıcı adı + şifre, her ikisi de gereklidir. Hayır ise → e-posta zaten 1. adımda toplandı.
Memnuniyet derecesi (1-5) ≥ 4 ise → “Neyi beğendin?” diye sorun. ≤ 2 ise → “Neyi geliştirebiliriz?” diye sorun.
4. Adım: İnceleme
Yalnızca toplam >= 100 ise görünür Son teslim.
Bu aşırı değil. Ancak mimari farklılıkları ortaya çıkarmak yeterlidir. Bölüm 1: Bileşen Odaklı (React Hook Formu + Zod) Kurulum npm react-hook-form zod'u kurun @hookform/resolvers @tanstack/react-query
Zod Şeması Zod şemasıyla başlayalım çünkü genellikle formun şekli burada oluşturulur. İlk iki adımda (kişisel ayrıntılar ve sipariş girişleri) her şey basittir: gerekli dizeler, minimumlu sayılar ve bir numaralandırma. İşin ilginç kısmı koşullu kuralları ifade etmeye çalıştığınızda başlıyor.
{ z }'yi "zod"dan içe aktarın;
const formSchema = z.object({ FirstName: z.string().min(1, "Gerekli"), email: z.string().email("Geçersiz e-posta"), fiyat: z.number().min(0), miktar: z.number().min(1), TaxRate: z.number(), hasAccount: z.enum(["Evet", "Hayır"]), kullanıcı adı: z.string().opsiyonel(), şifre: z.string().isteğe bağlı(), memnuniyet: z.number().min(1).max(5), pozitifFeedback: z.string().isteğe bağlı(), iyileştirmeFeedback: z.string().isteğe bağlı(),}).superRefine((data, ctx) => { if (data.hasAccount === "Evet") { if (!data.username) { ctx.addIssue({ code: "custom", path: ["kullanıcı adı"], mesaj: "Gerekli" }); if (!data.password || data.password.length < 6) { ctx.addIssue({ code: "özel", yol: ["şifre"], mesaj: "Min 6 karakter" } } }
if (data.satisfaction >= 4 && !data.positiveFeedback) { ctx.addIssue({ code: "custom", path: ["positiveFeedback"], message: "Lütfen beğendiklerinizi paylaşın" }); }
if (data.satisfaction <= 2 && !data.improvementFeedback) { ctx.addIssue({ code: "özel", yol:["improvementFeedback"], mesaj: "Lütfen bize neyi geliştirmemiz gerektiğini söyleyin" }); }});
dışa aktarma türü FormData = z.infer
Zod'un tür düzeyindeki şeması, alanların ne zaman önemli olduğunu belirleyen kuralları değil, nesnenin şeklini tanımladığından, koşullu olarak gerekli olmalarına rağmen kullanıcı adı ve parolanın isteğe bağlı() olarak yazıldığına dikkat edin. Koşullu gereksinimin, şekil doğrulandıktan sonra çalışan ve nesnenin tamamına erişebilen superRefine içinde bulunması gerekir. Bu ayrılık bir kusur değil; araç tam da bunun için tasarlandı: superRefine, şema yapısının kendisinde ifade edilemediğinde alanlar arası mantığın gittiği yerdir. Burada dikkat çeken şey, bu şemanın neyi ifade etmediğidir. Sayfa kavramı, hangi alanların hangi noktada görülebileceği kavramı ve gezinme kavramı yoktur. Bunların hepsi başka bir yerde yaşayacak. Form Bileşeni
"react-hook-form"dan { useForm, useWatch }'i içe aktar; "@hookform/resolvers/zod"'dan { zodResolver }'ı içe aktar; "@tanstack/react-query"'den { useMutation }'ı içe aktar; "react"'den { useState, useMemo }'yi içe aktar; "./schema"'dan { formSchema, type FormData }'yı içe aktar;
const ADIMLAR = ["ayrıntılar", "sipariş", "hesap", "inceleme"];
OrderPayload yazın = FormData & { subtotal: number; vergi: sayı; toplam: sayı };
dışa aktarma işlevi RHFMultiStepForm() { const [step, setStep] = useState(0);
const mutasyon = useMutation({ mutasyonFn: eşzamansız (yük: OrderPayload) => { const res = wait fetch("/api/orders", { yöntem: "POST", başlıklar: { "Content-Type": "application/json" }, gövde: JSON.stringify(yük), }); if (!res.ok) throw new Error("Gönderme başarısız oldu"); res.json()'u döndürün; }, });
const { kayıt, kontrol, tanıtıcıSubmit, formState: { hatalar }, } = useForm
return (
);}Altıncı neslin tükenmesiyle ilgili Pen SurveyJS-03-RHF'ye [çatallı] bakın. Burada pek çok şey oluyor ve işlerin nereye vardığını görmek için yavaşlamaya değer.
Türetilen değerler (alt toplam, vergi, toplam) bileşende useWatch ve useMemo aracılığıyla hesaplanır çünkü bunlar canlı alan değerlerine bağlıdır ve bunlar için başka doğal bir yer yoktur. Kullanıcı adı, parola, pozitif geri bildirim ve iyileştirme geri bildirimi için görünürlük kuralları, JSX'te satır içi koşullu olarak yayınlanır. Adım atlama mantığı (gözden geçirme sayfası yalnızca toplam >= 100 olduğunda görünür) showSubmit değişkenine ve 3. adımdaki oluşturma koşuluna yerleştirilmiştir. Gezinmenin kendisi yalnızca manuel olarak artırdığımız bir useState sayacıdır. React Query yeniden denemeleri, önbelleğe almayı ve geçersiz kılmayı yönetir. Form yalnızca doğrulanmış verilerle mutasyon.mutate'i çağırır.
Bunların hiçbiri kendi başına yanlış değil. Bu hala deyimsel React'tır ve RHF'nin yeniden oluşturmayı nasıl izole ettiği sayesinde bileşen oldukça performanslıdır. Ancak bunu yazmamış birine verirseniz ve inceleme sayfasının hangi koşullar altında göründüğünü açıklamasını isterseniz, tek satırda ifade edilebilecek bir kuralı yeniden oluşturmak için showSubmit'i, 3. adım oluşturma koşulunu ve gezinme düğmesi mantığını (üç ayrı yer) takip etmeleri gerekir. Form çalışıyor evet, ancak davranış bir sistem olarak gerçekten denetlenemez. Zihinsel olarak yürütülmesi gerekiyor. Daha da önemlisi, onu değiştirmek mühendislik katılımını gerektirir. İnceleme adımı göründüğünde ayarlama yapmak gibi küçük bir değişiklik bile bileşeni düzenlemek, doğrulamayı güncellemek, bir çekme isteği açmak, incelemeyi beklemek ve yeniden dağıtmak anlamına gelir. Bölüm 2: Şema Odaklı (SurveyJS) Şimdi aynı akışı bir şema kullanarak oluşturalım. Kurulum npm anket-çekirdek anket-tepki-ui'sini yükleyin @tanstack/react-query
Survey-core SurveyJS'nin form oluşturma işlemini destekleyen MIT lisanslı, platformdan bağımsız çalışma zamanı motoru; burada önemsediğimiz kısım. Bir JSON şeması alır, ondan dahili bir model oluşturur ve normalde React bileşeninizde yer alacak her şeyi yönetir: görünürlük ifadelerini değerlendirmek, türetilmiş değerleri hesaplamak, sayfa durumunu yönetmek, doğrulamayı izlemek ve gerçekte hangi sayfaların gösterildiğine göre "tamamlanmanın" ne anlama geldiğine karar vermek.
Survey-react-uiBu modeli React'e bağlayan kullanıcı arayüzü/oluşturma katmanı. Bu aslında motorun durumu değiştiğinde yeniden oluşturulan bir
Birlikte, tek bir kontrol akışı satırı yazmanıza gerek kalmadan, tamamen işlevsel, çok sayfalı bir form çalışma zamanı sağlarlar. Şema formatının kendisi, daha önce de söylediğimiz gibi, yalnızca bir JSON'dur; DSL veya tescilli herhangi bir şey yoktur. Bunu satır içi yapabilir, bir dosyadan içe aktarabilir, bir API'den alabilir veya bir veritabanı sütununda saklayabilir ve çalışma zamanında nemlendirebilirsiniz. Veri Olarak Aynı Form İşte aynı form, bu sefer bir JSON nesnesi olarak ifade edildi. Şema her şeyi tanımlar: yapı, doğrulama, görünürlük kuralları, türetilmiş hesaplamalar, sayfada gezinme - ve bunu çalışma zamanında değerlendirecek bir Modele aktarır. İşte tam olarak neye benziyor:
Export const SurveySchema = { title: "Sipariş Akışı", showProgressBar: "top", sayfalar: [ { name: "details", elements: [ { type: "text", name: "firstName", isRequired: true }, { type: "text", name: "email", inputType: "email", isRequired: true, validators: [{ type: "email", text: "Geçersiz e-posta" }] } ] }, { name: "order", elements: [ { type: "text", name: "price", inputType: "number", defaultValue: 0 }, { type: "text", name: "quantity", inputType: "number", defaultValue: 1 }, { type: "dropdown",ad: "vergiOranı", varsayılanDeğer: 0,1, seçimler: [ { değer: 0,05, metin: "%5" }, { değer: 0,1, metin: "%10" }, { değer: 0,15, metin: "%15" } ] }, { type: "ifade", ad: "alt toplam", ifade: "{fiyat} {miktar}" }, { tür: "ifade", ad: "vergi", ifade: "{alt toplam} {taxRate}" }, { type: "ifade", ad: "toplam", ifade: "{alt toplam} + {vergi}" } ] }, { name: "account", elements: [ { type: "radiogroup", name: "hasAccount", options: ["Evet", "Hayır"] }, { type: "text", name: "kullanıcı adı", görünürIf: "{hasAccount} = 'Evet'", isRequired: true }, { type: "text", name: "password", inputType: "password", görünürIf: "{hasAccount} = 'Evet'", isRequired: true, validators: [{ type: "text", minLength: 6, text: "Min 6 karakter" }] }, { type: "rating", name: "tatmin", oranMin: 1, oranMax: 5 }, { type: "yorum", ad: "pozitifGeribildirim", görünürIf: "{satisfaction} >= 4" }, { type: "yorum", ad: "iyileştirmeGeribildirim", görünürIf: "{memnuniyet} <= 2" } ] }, { name: "inceleme", görünürIf: "{toplam} >= 100", öğeler: [] } ]};
Bir an için bunu RHF versiyonuyla karşılaştırın.
Koşullu olarak kullanıcı adı ve parola gerektiren superRefine bloğu kaldırıldı. Görünür If: "{hasAccount} = 'Evet'" isRequired: true ile birleştiğinde, her iki kaygıyı da, onları bulmayı beklediğiniz alanda, birlikte ele alır. Alt toplamı, vergiyi ve toplamı hesaplayan useWatch + useMemo zincirinin yerini, birbirlerine adlarıyla başvuran üç ifade alanı aldı. RHF sürümünde yalnızca 3. adım oluşturma dalı olan showSubmit aracılığıyla izlenerek yeniden oluşturulabilen inceleme sayfası koşulu. Ve son olarak, gezinme düğmesi mantığı, sayfa nesnesindeki tek bir görünür If özelliğidir.
Aynı mantık orada da mevcut. Sadece şema, bileşene yayılmak yerine, izole olarak görülebileceği bir yaşam alanı sağlıyor. Ayrıca şemanın alt toplam, vergi ve toplam için type: 'expression' kullandığını unutmayın. İfade salt okunurdur ve esas olarak hesaplanan değerleri görüntülemek için kullanılır. SurveyJS ayrıca statik içerik için type: 'html'yi de destekler, ancak hesaplanan değerler için ifade doğru seçimdir. Şimdi React tarafına geçelim. Oluşturma ve Gönderim Çok basit. onComplete'i API'nize aynı şekilde - useMutation veya düz getirme yoluyla bağlayın:
"react" öğesinden { useState, useEffect, useRef } içe aktarın; "@tanstack/react-query" öğesinden { useMutation } öğesini içe aktarın; "survey-core" öğesinden { Model } öğesini içe aktarın; "survey-react-ui" öğesinden { Survey } öğesini içe aktarın; "survey-core/survey-core.css" öğesini içe aktarın;
dışa aktarma işlevi SurveyForm() { const [model] = useState(() => new Model(surveySchema));
const mutasyon = useMutation({ mutasyonFn: eşzamansız (veri) => { const res = wait fetch("/api/orders", { yöntem: "POST", başlıklar: { "Content-Type": "application/json" }, gövde: JSON.stringify(veri), }); if (!res.ok) throw new Error("Gönderme başarısız oldu"); res.json()'u döndürün; }, });
const mutasyonRef = useRef(mutasyon); mutasyonRef.current = mutasyon; useEffect(() => { const işleyici = (gönderen) => mutasyonRef.current.mutate(gönderici.data); model.onComplete.add(işleyici); return () => model.onComplete.remove(işleyici); }, [model]); // ref, her işlemede işleyicinin yeniden kaydedilmesini önler (mutasyon nesnesi kimliği değişir)
dönüş (
<>
Sixtinction tarafından kalem anketiJS-03-SurveyJS'ye [çatallı] bakın.
onComplete, kullanıcı son görünür sayfanın sonuna ulaştığında tetiklenir. Dolayısıyla, toplam 100'ü asla geçmezse ve inceleme sayfası atlanırsa yine de doğru şekilde tetiklenir çünkü SurveyJS, "son sayfanın" ne anlama geldiğine karar vermeden önce görünürlüğü değerlendirir. Daha sonra sender.data, birinci sınıf alanlar olarak hesaplanan değerlerle (alt toplam, vergi, toplam) birlikte tüm yanıtları içerir; dolayısıyla API yükü, onSubmit'te manuel olarak derlenen RHF sürümününkiyle aynıdır. mutasyonRef modeli, her işlemede değişen bir değer üzerinde kararlı bir olay işleyicisine ihtiyaç duyduğunuz her yerde ulaşacağınız modelle aynıdır; SurveyJS'e özgü bir şey değildir.
React bileşeni artık hiçbir iş mantığı içermiyor. UseWatch yok, koşullu JSX yok, adım sayacı yok, useMemo zinciri yok, superRefine yok. React, aslında iyi olduğu şeyi yapıyor: bir bileşeni oluşturmak ve onu bir API çağrısına bağlamak. React'tan Ne Çıktı?
endişe RHF Yığını SurveyJS Görünürlük JSX şubeleri görünür ise Türetilmiş değerler useWatch / useMemo ifade Alanlar arası kurallar süper rafine Şema koşulları Navigasyon adım durumu Sayfa görünürIf Kural konumu Dosyalar arasında dağıtılmış Şemada merkezileştirilmiş
React'ta kalan şey düzen, stil, gönderim kablolaması ve uygulama entegrasyonudur; yani React'in gerçekte tasarlandığı şeylerdir. Geriye kalan her şey şemaya taşındı ve şema yalnızca bir JSON nesnesi olduğundan, bir veritabanında saklanabilir, uygulama kodunuzdan bağımsız olarak sürümlendirilebilir veya dağıtım gerektirmeden dahili araçlar aracılığıyla düzenlenebilir. İnceleme sayfasını tetikleyen eşiği değiştirmesi gereken bir ürün yöneticisi, bunu bileşene dokunmadan yapabilir. Bu, form davranışının sıklıkla geliştiği ve her zaman mühendisler tarafından yönlendirilmediği ekipler için anlamlı bir operasyonel farktır. Her Yaklaşım Ne Zaman Kullanılmalı? İşte benim için işe yarayan iyi bir genel kural: Formu tamamen sildiğinizi hayal edin. Ne kaybedersin?
Eğer ekranlar ise, bileşen odaklı formlar istersiniz. Gerçek kararları kodlayan eşikler, dallanma kuralları ve koşullu gereksinimler gibi iş mantığıysa bir şema motoru istersiniz.
Benzer şekilde, önünüze çıkan değişiklikler çoğunlukla etiketler, alanlar ve düzen ile ilgiliyse, RHF size iyi hizmet edecektir. Operasyonlarınızın veya hukuk ekibinizin Salı öğleden sonra herhangi bir bildirimde bulunmadan ayarlaması gerekebilecek koşullar, sonuçlar ve kurallarla ilgiliyse SurveyJS'nin şema modeli daha dürüst bir seçimdir. Bu iki yaklaşım aslında birbiriyle rekabet halinde değildir. Farklı sorun sınıflarına hitap ediyorlar ve kaçınılması gereken hata, soyutlamayı mantığın ağırlığıyla eşleştirmemektir - tanıdık bir araç olduğu için bir kural sistemine bir bileşen gibi davranmak veya bir form üç adıma ulaştığı ve koşullu bir alan kazandığı için bir politika motoruna ulaşmak. Burada oluşturduğumuz form kasıtlı olarak sınıra yakın duruyor; farkı açığa çıkaracak kadar karmaşık ama karşılaştırmanın hileli olduğu hissini verecek kadar aşırı değil. Kod tabanınızda kullanışsız hale gelen gerçek formların çoğu muhtemelen aynı sınırın yakınında yer alır ve soru genellikle birinin gerçekte ne olduklarını adlandırıp adlandırmadığıdır. React Hook Form + Zod'u şu durumlarda kullanın:
Formlar CRUD odaklıdır; Mantık yüzeyseldir ve kullanıcı arayüzüne dayalıdır; Mühendisler tüm davranışların sahibidir; Arka uç gerçeğin kaynağı olmaya devam ediyor.
SurveyJS'yi şu durumlarda kullanın:
Formlar iş kararlarını kodlar; Kurallar kullanıcı arayüzünden bağımsız olarak gelişir; Mantık görünür, denetlenebilir veya versiyonlanmış olmalıdır; Mühendis olmayanlar davranışı etkiler; Aynı formun birden fazla ön uçta çalışması gerekir.