Kendi React Validation Kitaplığınızı Oluşturma: Temel Bilgiler (Bölüm 1)

Yayınlanan: 2022-03-10
Kısa özet ↬ Doğrulama kitaplıklarının nasıl çalıştığını hiç merak ettiniz mi? Bu makale size adım adım React için kendi doğrulama kitaplığınızı nasıl oluşturacağınızı anlatacaktır. Sonraki bölüm, bazı daha gelişmiş özellikler ekleyecek ve son bölüm, geliştirici deneyimini geliştirmeye odaklanacak.

Form doğrulama kitaplıklarının her zaman oldukça havalı olduğunu düşündüm. Biliyorum, sahip olmak niş bir ilgi - ama onları çok fazla kullanıyoruz! En azından işimde - yaptığım şeylerin çoğu, daha önceki seçimlere ve yollara bağlı olan doğrulama kurallarıyla az çok karmaşık formlar oluşturmaktır. Bir form doğrulama kitaplığının nasıl çalışacağını anlamak çok önemlidir.

Geçen yıl, böyle bir form doğrulama kitaplığı yazdım. Adını “Kalidasyon” koydum ve giriş blog yazısını buradan okuyabilirsiniz. Çok fazla esneklik sunan ve piyasadaki diğerlerinden biraz farklı bir yaklaşım kullanan iyi bir kitaplık. Yine de dışarıda tonlarca harika kütüphane var - benimki gereksinimlerimiz için iyi çalıştı.

Bugün size React için kendi doğrulama kitaplığınızı nasıl yazacağınızı göstereceğim. Süreci adım adım inceleyeceğiz ve ilerledikçe CodeSandbox örneklerini bulacaksınız. Bu makalenin sonunda, kendi doğrulama kitaplığınızı nasıl yazacağınızı öğreneceksiniz veya en azından diğer kitaplıkların "doğrulama büyüsünü" nasıl uyguladığına dair daha derin bir anlayışa sahip olacaksınız.

  • Bölüm 1: Temel Bilgiler
  • Bölüm 2: Özellikler
  • Bölüm 3: Deneyim
Atlamadan sonra daha fazlası! Aşağıdan okumaya devam edin ↓

Adım 1: API'yi Tasarlama

Herhangi bir kitaplık oluşturmanın ilk adımı, nasıl kullanılacağını tasarlamaktır. Gelecek pek çok çalışmanın temelini oluşturuyor ve bence kitaplığınızda vereceğiniz en önemli karar bu.

"Kullanımı kolay" ve gelecekteki iyileştirmelere ve gelişmiş kullanım durumlarına izin verecek kadar esnek bir API oluşturmak önemlidir. Bu iki hedefi de yakalamaya çalışacağız.

Tek bir yapılandırma nesnesini kabul edecek özel bir kanca oluşturacağız. Bu, gelecekteki seçeneklerin kırılma değişiklikleri getirmeden geçmesine izin verecektir.

Kancalar Üzerine Bir Not

Hooks, React yazmanın oldukça yeni bir yoludur. Geçmişte React yazdıysanız, bu kavramlardan birkaçını tanımamış olabilirsiniz. Bu durumda, lütfen resmi belgelere bir göz atın. İnanılmaz derecede iyi yazılmış ve bilmeniz gereken temel bilgileri size sunuyor.

Şimdilik özel kanca useValidation . Kullanımı şöyle görünebilir:

 const config = { fields: { username: { isRequired: { message: 'Please fill out a username' }, }, password: { isRequired: { message: 'Please fill out a password' }, isMinLength: { value: 6, message: 'Please make it more secure' } } }, onSubmit: e => { /* handle submit */ } }; const { getFieldProps, getFormProps, errors } = useValidation(config);

config nesnesi, her alan için doğrulama kurallarını ayarlayan bir fields prop'unu kabul eder. Ayrıca, form gönderildiğinde bir geri aramayı kabul eder.

fields nesnesi, doğrulamak istediğimiz her alan için bir anahtar içerir. Her alanın, her anahtarın bir doğrulayıcı adı olduğu ve her değerin o doğrulayıcı için bir yapılandırma özelliği olduğu kendi yapılandırması vardır. Aynı şeyi yazmanın başka bir yolu da şudur:

 { fields: { fieldName: { oneValidator: { validatorRule: 'validator value' }, anotherValidator: { errorMessage: 'something is not as it should' } } } }

useValidation , getFieldProps , getFormProps ve errors gibi birkaç özelliğe sahip bir nesne döndürür. İlk iki işlev, Kent C. Dodds'un "pervane alıcıları" dediği şeydir (bunlarla ilgili harika bir makale için buraya bakın) ve belirli bir form alanı veya form etiketi için ilgili donanımları almak için kullanılır. errors prop, alan başına anahtarlanmış herhangi bir hata mesajı içeren bir nesnedir.

Bu kullanım şöyle görünecektir:

 const config = { ... }; // like above const LoginForm = props => { const { getFieldProps, getFormProps, errors } = useValidation(config); return ( <form {...getFormProps()}> <label> Username<br/> <input {...getFieldProps('username')} /> {errors.username && <div className="error">{errors.username}</div>} </label> <label> Password<br/> <input {...getFieldProps('password')} /> {errors.password && <div className="error">{errors.password}</div>} </label> <button type="submit">Submit my form</button> </form> ); };

Pekala! Böylece API'yi çiviledik.

  • CodeSandbox demosuna bakın

Ayrıca useValidation kancasının sahte bir uygulamasını oluşturduğumuzu unutmayın. Şimdilik, sadece orada olmamız gereken nesneler ve işlevler içeren bir nesneyi döndürüyor, bu nedenle örnek uygulamamızı bozmayız.

Form Durumunun Saklanması

Yapmamız gereken ilk şey, tüm form durumunu özel kancamızda saklamak. Her alanın değerlerini, herhangi bir hata mesajını ve formun gönderilip gönderilmediğini hatırlamamız gerekiyor. Bunun için en fazla esnekliğe (ve daha az ortak plakaya) izin verdiği için useReducer kancasını kullanacağız. Redux'u daha önce kullandıysanız, bazı tanıdık kavramlar göreceksiniz - yoksa, ilerledikçe açıklayacağız! useReducer kancasına iletilen bir redüktör yazarak başlayacağız:

 const initialState = { values: {}, errors: {}, submitted: false, }; function validationReducer(state, action) { switch(action.type) { case 'change': const values = { ...state.values, ...action.payload }; return { ...state, values, }; case 'submit': return { ...state, submitted: true }; default: throw new Error('Unknown action type'); } }

Redüktör Nedir?

Bir indirgeyici, bir değerler nesnesi ve bir "eylem" kabul eden ve değerler nesnesinin artırılmış bir sürümünü döndüren bir işlevdir.

Eylemler, type özelliğine sahip düz JavaScript nesneleridir. Her olası eylem türünü işlemek için bir switch ifadesi kullanıyoruz.

"Değer nesnesi" genellikle durum olarak adlandırılır ve bizim durumumuzda bu, doğrulama mantığımızın durumudur.

Durumumuz üç veri parçasından oluşur - values (form alanlarımızın mevcut değerleri), errors (geçerli hata mesajları grubu) ve formumuzun en az bir kez gönderilip gönderilmediğini gösteren bir bayrak isSubmitted .

Form durumumuzu saklamak için useValidation birkaç bölümünü uygulamamız gerekiyor. getFieldProps yöntemimizi çağırdığımızda, o alanın değerine sahip bir nesne, değiştiği zaman için bir değişiklik işleyicisi ve hangi alanın hangisi olduğunu izlemek için bir ad prop döndürmemiz gerekiyor.

 function validationReducer(state, action) { // Like above } const initialState = { /* like above */ }; const useValidation = config => { const [state, dispatch] = useReducer(validationReducer, initialState); return { errors: state.errors, getFormProps: e => {}, getFieldProps: fieldName => ({ onChange: e => { if (!config.fields[fieldName]) { return; } dispatch({ type: 'change', payload: { [fieldName]: e.target.value } }); }, name: fieldName, value: state.values[fieldName], }), }; };

getFieldProps yöntemi artık her alan için gerekli olan malzemeleri döndürür. Bir değişiklik olayı başlatıldığında, alanın doğrulama yapılandırmamızda olduğundan emin oluruz ve ardından redüktörümüze bir change eyleminin gerçekleştiğini bildiririz. Redüktör, doğrulama durumundaki değişiklikleri işleyecektir.

  • CodeSandbox demosuna bakın

Formumuzu Doğrulama

Form doğrulama kitaplığımız iyi görünüyor, ancak form değerlerimizi doğrulama açısından pek bir şey yapmıyor! Bunu düzeltelim.

Her değişiklik olayındaki tüm alanları doğrulayacağız. Bu kulağa çok verimli gelmeyebilir, ancak karşılaştığım gerçek dünya uygulamalarında bu gerçekten bir sorun değil.

Dikkat edin, her değişiklikte her hatayı göstermeniz gerektiğini söylemiyoruz. Bu makalenin ilerleyen kısımlarında, yalnızca bir alanı gönderdiğinizde veya bir alandan ayrıldığınızda hataların nasıl gösterileceğini yeniden ele alacağız.

Doğrulayıcı İşlevleri Nasıl Seçilir

Doğrulayıcılar söz konusu olduğunda, ihtiyacınız olan tüm doğrulama yöntemlerini uygulayan tonlarca kitaplık var. İsterseniz kendiniz de yazabilirsiniz. Bu eğlenceli bir egzersiz!

Bu proje için, bir süre önce yazdığım bir dizi doğrulayıcı kullanacağız - calidators . Bu doğrulayıcılar aşağıdaki API'ye sahiptir:

 function isRequired(config) { return function(value) { if (value === '') { return config.message; } else { return null; } }; } // or the same, but terser const isRequired = config => value => value === '' ? config.message : null;

Başka bir deyişle, her doğrulayıcı bir yapılandırma nesnesini kabul eder ve tam olarak yapılandırılmış bir doğrulayıcı döndürür. Bu işlev bir değerle çağrıldığında, değer geçersizse prop message , geçerliyse null değerini döndürür. Kaynak koduna bakarak bu doğrulayıcılardan bazılarının nasıl uygulandığına bakabilirsiniz.

Bu doğrulayıcılara erişmek için, calidators paketini npm install calidators ile kurun.

Tek bir alanı doğrulama

useValidation yapılandırmayı hatırlıyor musunuz? Şuna benziyor:

 { fields: { username: { isRequired: { message: 'Please fill out a username' }, }, password: { isRequired: { message: 'Please fill out a password' }, isMinLength: { value: 6, message: 'Please make it more secure' } } }, // more stuff }

Uygulamamızı basitleştirmek için, doğrulamak için yalnızca tek bir alanımız olduğunu varsayalım. Alanın yapılandırma nesnesinin her bir anahtarını gözden geçireceğiz ve doğrulayıcıları bir hata bulana veya doğrulama bitene kadar tek tek çalıştıracağız.

 import * as validators from 'calidators'; function validateField(fieldValue = '', fieldConfig) { for (let validatorName in fieldConfig) { const validatorConfig = fieldConfig[validatorName]; const validator = validators[validatorName]; const configuredValidator = validator(validatorConfig); const errorMessage = configuredValidator(fieldValue); if (errorMessage) { return errorMessage; } } return null; }

Burada, doğrulamak için değeri kabul eden ve bu alan için doğrulayıcı yapılandırmalarını kabul eden bir validateField işlevi yazdık. Tüm doğrulayıcılar arasında dolaşıyoruz, onlara bu doğrulayıcı için yapılandırmayı iletiyoruz ve çalıştırıyoruz. Bir hata mesajı alırsak, diğer doğrulayıcıları atlayıp geri dönüyoruz. Değilse, bir sonraki doğrulayıcıyı deneriz.

Not: Doğrulayıcı API'lerinde

Farklı API'lere sahip farklı doğrulayıcılar seçerseniz (çok popüler validator.js gibi), kodunuzun bu kısmı biraz farklı görünebilir. Ancak, kısaca olsun diye, bu kısmın okuyucuya bırakılmış bir alıştırma olmasına izin verdik.

Not: Açık for…in döngüleri

for...in döngülerinde daha önce hiç kullanılmadı mı? Sorun değil, bu benim de ilk seferimdi! Temel olarak, bir nesnedeki anahtarlar üzerinde yinelenir. Onlar hakkında daha fazla bilgiyi MDN'de okuyabilirsiniz.

Tüm alanları doğrula

Artık bir alanı doğruladığımıza göre, çok fazla sorun yaşamadan tüm alanları doğrulayabilmeliyiz.

 function validateField(fieldValue = '', fieldConfig) { // as before } function validateFields(fieldValues, fieldConfigs) { const errors = {}; for (let fieldName in fieldConfigs) { const fieldConfig = fieldConfigs[fieldName]; const fieldValue = fieldValues[fieldName]; errors[fieldName] = validateField(fieldValue, fieldConfig); } return errors; }

Tüm alan değerlerini ve tüm alan yapılandırmasını kabul eden bir validateFields işlevi yazdık. Yapılandırmadaki her alan adı arasında dolaşıyoruz ve bu alanı yapılandırma nesnesi ve değeriyle doğrularız.

Sonraki: Redüktörümüze söyleyin

Pekala, şimdi tüm eşyalarımızı doğrulayan bu fonksiyona sahibiz. Hadi onu kodumuzun geri kalanına çekelim!

İlk olarak, validationReducer bir validate eylem işleyicisi ekleyeceğiz.

 function validationReducer(state, action) { switch (action.type) { case 'change': // as before case 'submit': // as before case 'validate': return { ...state, errors: action.payload }; default: throw new Error('Unknown action type'); } }

Doğrulama eylemini her tetiklediğimizde, validate hataları eylemin yanında iletilenlerle değiştiririz.

Sırada, bir useEffect kancasından doğrulama mantığımızı tetikleyeceğiz:

 const useValidation = config => { const [state, dispatch] = useReducer(validationReducer, initialState); useEffect(() => { const errors = validateFields(state.fields, config.fields); dispatch({ type: 'validate', payload: errors }); }, [state.fields, config.fields]); return { // as before }; };

Bu useEffect kancası, ilk bağlamaya ek olarak state.fields veya config.fields değiştiğinde çalışır.

Hataya Dikkat

Yukarıdaki kodda süper ince bir hata var. useEffect yalnızca state.fields veya config.fields değiştiğinde yeniden çalışması gerektiğini belirttik. Görünen o ki, “değişim” mutlaka değerde bir değişiklik anlamına gelmez! useEffect , nesneler arasında eşitliği sağlamak için Object.is kullanır ve bu da referans eşitliğini kullanır. Yani, aynı içeriğe sahip yeni bir nesne iletirseniz, aynı olmayacaktır (çünkü nesnenin kendisi yenidir).

state.fields , bize bu referans eşitliğini garanti eden useReducer döndürülür, ancak config , işlev bileşenimizde satır içi olarak belirtilir. Bu, nesnenin her işlemede yeniden oluşturulduğu anlamına gelir ve bu da yukarıdaki useEffect tetikler!

Bunu çözmek için Kent C. Dodds'un use-deep-compare-effect kitaplığını kullanmamız gerekiyor. Bunu npm install use-deep-compare-effect ile kurarsınız ve bunun yerine useEffect çağrınızı bununla değiştirirsiniz. Bu, referans eşitlik kontrolü yerine derin bir eşitlik kontrolü yapmamızı sağlar.

Kodunuz şimdi şöyle görünecek:

 import useDeepCompareEffect from 'use-deep-compare-effect'; const useValidation = config => { const [state, dispatch] = useReducer(validationReducer, initialState); useDeepCompareEffect(() => { const errors = validateFields(state.fields, config.fields); dispatch({ type: 'validate', payload: errors }); }, [state.fields, config.fields]); return { // as before }; };

Kullanım Etkisi Üzerine Bir Not

Görünüşe göre useEffect oldukça ilginç bir işlev. Dan Abramov, bu kanca hakkında her şeyi öğrenmekle ilgileniyorsanız, useEffect incelikleri hakkında gerçekten güzel, uzun bir makale yazdı.

Artık işler bir doğrulama kitaplığı gibi görünmeye başlıyor!

  • CodeSandbox demosuna bakın

İşlem Formu Gönderimi

Temel form doğrulama kitaplığımızın son parçası, formu gönderdiğimizde ne olacağını ele almaktır. Şu anda sayfayı yeniden yüklüyor ve hiçbir şey olmuyor. Bu optimal değil. Formlar söz konusu olduğunda varsayılan tarayıcı davranışını önlemek ve bunun yerine kendimiz halletmek istiyoruz. Bu mantığı getFormProps prop alıcı işlevinin içine yerleştiririz:

 const useValidation = config => { const [state, dispatch] = useReducer(validationReducer, initialState); // as before return { getFormProps: () => ({ onSubmit: e => { e.preventDefault(); dispatch({ type: 'submit' }); if (config.onSubmit) { config.onSubmit(state); } }, }), // as before }; };

getFormProps submit , send DOM olayı tetiklendiğinde tetiklenen bir onSubmit işlevi döndürecek şekilde değiştiriyoruz. Varsayılan tarayıcı davranışını engelleriz, indiricimize gönderdiğimizi bildirmek için bir eylem göndeririz ve sağlanan onSubmit geri aramasını, sağlanmışsa, durumun tamamıyla birlikte ararız.

Özet

Vardı! Basit, kullanışlı ve oldukça havalı bir doğrulama kitaplığı oluşturduk. Yine de, interweb'lere hakim olmadan önce yapılacak tonlarca iş var.

  • Bölüm 1: Temel Bilgiler
  • Bölüm 2: Özellikler
  • Bölüm 3: Deneyim