React Hooks ve TypeScript ile Çalışmak

Yayınlanan: 2022-03-11

Kancalar, kod okunabilirliğini iyileştirmek için React'e Şubat 2019'da tanıtıldı. React kancalarını önceki makalelerde zaten tartışmıştık, ancak bu sefer kancaların TypeScript ile nasıl çalıştığını inceliyoruz.

Kancalardan önce, React bileşenlerinin iki çeşidi vardı:

  • Bir durumu işleyen sınıflar
  • Donanımları tarafından tam olarak tanımlanan işlevler

Bunların doğal bir kullanımı, sınıflarla karmaşık kap bileşenleri ve saf işlevlere sahip basit sunum bileşenleri oluşturmaktı.

Tepki Kancaları Nedir?

Konteyner bileşenleri, durum yönetimini ve sunucuya yapılan istekleri yönetir, bu daha sonra bu makalede yan etkiler olarak adlandırılacaktır. Durum, kapsayıcı çocuklara aksesuarlar aracılığıyla yayılacaktır.

Tepki Kancaları Nedir?

Ancak kod büyüdükçe, işlevsel bileşenler kapsayıcı bileşenler olarak dönüştürülme eğilimindedir.

İşlevsel bir bileşeni daha akıllı bir bileşene yükseltmek o kadar acı verici değil, ancak zaman alıcı ve hoş olmayan bir iştir. Ayrıca, sunucuları ve kapsayıcıları çok katı bir şekilde ayırt etmek artık hoş karşılanmıyor.

Kancalar her ikisini de yapabilir, bu nedenle ortaya çıkan kod daha tekdüzedir ve neredeyse tüm avantajlara sahiptir. Burada, bir teklif imzasını işleyen küçük bir bileşene yerel bir durum ekleme örneği verilmiştir.

 // put signature in local state and toggle signature when signed changes function QuotationSignature({quotation}) { const [signed, setSigned] = useState(quotation.signed); useEffect(() => { fetchPost(`quotation/${quotation.number}/sign`) }, [signed]); // effect will be fired when signed changes return <> <input type="checkbox" checked={signed} onChange={() => {setSigned(!signed)}}/> Signature </> }

Bunun büyük bir avantajı var: TypeScript ile kodlama Angular ile harikaydı ama React ile şişirildi. Ancak, React kancalarını TypeScript ile kodlamak hoş bir deneyimdir.

Eski React ile TypeScript

TypeScript, Microsoft tarafından tasarlandı ve React, şu anda çekişini kaybeden Flow'u geliştirdiğinde Angular yolunu izledi. Saf TypeScript ile React sınıfları yazmak oldukça acı vericiydi çünkü React geliştiricilerinin birçok anahtar aynı olmasına rağmen hem props hem de state yazması gerekiyordu.

İşte basit bir etki alanı nesnesi. Durum ve aksesuarlar ile bazı kaba bileşenlerde yönetilen bir Quotation türü ile bir teklif uygulaması yapıyoruz. Quotation oluşturulabilir ve ilgili durumu imzalı veya imzasız olarak değiştirilebilir.

 interface Quotation{ id: number title:string; lines:QuotationLine[] price: number } interface QuotationState{ readonly quotation:Quotation; signed: boolean } interface QuotationProps{ quotation:Quotation; } class QuotationPage extends Component<QuotationProps, QuotationState> { // ... }

Ancak, QuotationPage'in şimdi sunucudan bir kimlik isteyeceğini düşünün: örneğin, şirket tarafından yapılan 678. teklif. Bu, QuotationProps'un bu önemli sayıyı bilmediği anlamına gelir - bir Quotation'u tam olarak sarmaz. QuotationProps arayüzünde çok daha fazla kod bildirmemiz gerekiyor:

 interface QuotationProps{ // ... all the attributes of Quotation but id title:string; lines:QuotationLine[] price: number }

Kimlik dışındaki tüm özellikleri yeni bir türe kopyalarız. hm. Bu bana bir sürü DTO yazmayı içeren eski Java'yı düşündürüyor. Bunun üstesinden gelmek için, acıyı atlamak için TypeScript bilgimizi artıracağız.

Hook'lu TypeScript'in Faydaları

Kancaları kullanarak, önceki QuotationState arayüzünden kurtulabileceğiz. Bunu yapmak için, QuotationState'i devletin iki farklı bölümüne böleceğiz.

 interface QuotationProps{ quotation:Quotation; } function QuotationPage({quotation}:QuotationProps){ const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); }

Durumu bölerek, yeni arayüzler oluşturmak zorunda değiliz. Yerel durum türleri genellikle varsayılan durum değerleriyle belirlenir.

Kancalı bileşenlerin tümü birer işlevdir. Böylece, React kitaplığında tanımlanan FC<P> tipini döndürerek aynı bileşeni yazabiliriz. İşlev, props türü boyunca ayarlayarak dönüş türünü açıkça bildirir.

 const QuotationPage : FC<QuotationProps> = ({quotation}) => { const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); }

Açıkçası, TypeScript'i React kancalarıyla kullanmak, onu React sınıflarıyla kullanmaktan daha kolaydır. Güçlü yazma, kod güvenliği için değerli bir güvenlik olduğundan, yeni projeniz kanca kullanıyorsa TypeScript kullanmayı düşünmelisiniz. TypeScript istiyorsanız kesinlikle kanca kullanmalısınız.

React kullanarak veya kullanmayarak TypeScript'ten kaçınmanızın birçok nedeni vardır. Ama kullanmayı seçerseniz, kesinlikle kancaları da kullanmalısınız.

Hook'lara Uygun TypeScript'in Spesifik Özellikleri

Önceki React kancaları TypeScript örneğinde, QuotationProps'ta hala number niteliğine sahibim, ancak bu sayının gerçekte ne olduğuna dair henüz bir ipucu yok.

TypeScript bize uzun bir yardımcı program türleri listesi verir ve bunlardan üçü, birçok arayüz açıklamasının gürültüsünü azaltarak React'te bize yardımcı olur.

  • Partial<T> : T'nin herhangi bir alt anahtarı
  • Omit<T, 'x'> : x hariç T'nin tüm anahtarları
  • Pick<T, 'x', 'y', 'z'> : T tam x, y, z tuşları

Hook'lara Uygun TypeScript'in Spesifik Özellikleri

Bizim durumumuzda, Omit<Quotation, 'id'> öğesinin alıntının kimliğini atlamasını istiyoruz. type anahtar sözcüğü ile anında yeni bir tür oluşturabiliriz.

Partial<T> ve Omit<T> , Java gibi çoğu yazılan dilde mevcut değildir, ancak ön uç geliştirmede Forms ile örnekler için çok yardımcı olur. Yazma yükünü kolaylaştırır.

 type QuotationProps= Omit<Quotation, id>; function QuotationPage({quotation}:QuotationProps){ const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); // ... }

Şimdi kimliği olmayan bir Teklifimiz var. Böylece, belki bir Quotation tasarlayabiliriz ve PersistedQuotation , Quotation extends . Ayrıca, bazı tekrarlayan if veya undefined sorunları kolayca çözeceğiz. Tam nesne olmasa da, yine de değişken alıntıyı çağırmalı mıyız? Bu, bu makalenin kapsamını aşıyor, ancak yine de daha sonra bahsedeceğiz.

Ancak artık number olduğunu düşündüğümüz bir nesneyi yaymayacağımızdan eminiz. Partial<T> kullanmak tüm bu garantileri getirmez, bu yüzden dikkatli kullanın.

Pick<T, 'x'|'y'> , yeni bir arayüz bildirmek zorunda kalmadan anında bir tür bildirmenin başka bir yoludur. Bir bileşen ise, Teklif başlığını düzenlemeniz yeterlidir:

 type QuoteEditFormProps= Pick<Quotation, 'id'|'title'>

Ya da sadece:

 function QuotationNameEditor({id, title}:Pick<Quotation, 'id'|'title'>){ ...}

Beni yargılama, Etki Alanına Dayalı Tasarımın büyük bir hayranıyım. Yeni bir arayüz için iki satır daha yazmak istemeyecek kadar tembel değilim. Etki alanı adlarını tam olarak tanımlamak için arayüzler kullanıyorum ve bu yardımcı program, gürültüyü önleyerek yerel kodun doğruluğu için işlev görüyor. Okuyucu, Quotation kurallı arayüz olduğunu bilecektir.

React Hook'ların Diğer Faydaları

React ekibi, React'i her zaman işlevsel bir çerçeve olarak gördü ve ele aldı. Bir bileşenin kendi durumunu idare edebilmesi için sınıfları kullandılar ve şimdi bir fonksiyonun bileşen durumunu takip etmesine izin veren bir teknik olarak kancalar.

 interface Place{ city:string, country:string } const initialState:Place = { city: 'Rosebud', country: 'USA' }; function reducer(state:Place, action):Partial<Place> { switch (action.type) { case 'city': return { city: action.payload }; case 'country': return { country: action.payload }; } } function PlaceForm() { const [state, dispatch] = useReducer(reducer, initialState); return ( <form> <input type="text" name="city" onChange={(event) => { dispatch({ type: 'city',payload: event.target.value}) }} value={state.city} /> <input type="text" name="country" onChange={(event) => { dispatch({type: 'country', payload: event.target.value }) }} value={state.country} /> </form> ); }

İşte Partial kullanmanın güvenli ve iyi bir seçim olduğu bir durum.

Bir işlev birçok kez çalıştırılabilse de, ilişkili useReducer kancası yalnızca bir kez oluşturulacaktır.

Doğal olarak indirgeyici işlevi bileşenden çıkararak, kod bir sınıf içindeki birden çok işlev yerine, tümü sınıf içindeki duruma bağlı birden çok bağımsız işleve bölünebilir.

Bu, test edilebilirlik için açıkça daha iyidir - bazı işlevler JSX ile, diğerleri davranışla, diğerleri iş mantığıyla vb. ilgilenir.

Artık (neredeyse) Yüksek Dereceli Bileşenlere ihtiyacınız yok. Render props desenini fonksiyonlarla yazmak daha kolaydır.

Yani kodu okumak daha kolay. Kodunuz bir sınıflar/işlevler/kalıplar akışı değil, bir işlevler akışıdır. Ancak, işlevleriniz bir nesneye bağlı olmadığı için tüm bu işlevleri adlandırmak zor olabilir.

TypeScript Hala JavaScript'tir

JavaScript eğlencelidir çünkü kodunuzu herhangi bir yönde yırtabilirsiniz. TypeScript ile, nesnelerin tuşlarıyla oynamak için hala keyof kullanabilirsiniz. Okunamaz ve sürdürülemez bir şey yaratmak için tür birliklerini kullanabilirsiniz - hayır, bunlardan hoşlanmıyorum. Bir dizenin bir UUID olduğunu varsaymak için tür diğer adını kullanabilirsiniz.

Ancak bunu sıfır güvenlikle yapabilirsiniz. tsconfig.json dosyanızın "strict":true seçeneğine sahip olduğundan emin olun. Proje başlamadan önce kontrol edin, yoksa hemen hemen her satırı yeniden düzenlemeniz gerekecek!

Kodunuza koyduğunuz yazma düzeyiyle ilgili tartışmalar var. Her şeyi yazabilir veya derleyicinin türleri çıkarmasına izin verebilirsiniz. Linter konfigürasyonuna ve takım tercihlerine bağlıdır.

Ayrıca, yine de çalışma zamanı hataları yapabilirsiniz! TypeScript, Java'dan daha basittir ve Generics ile kovaryans/çelişki sorunlarından kaçınır.

Bu Hayvan/Kedi örneğinde, Kedi listesiyle aynı olan bir Hayvan listemiz var. Ne yazık ki, ikinci değil, ilk satırdaki bir sözleşme. Daha sonra Animal listesine bir ördek ekleriz, böylece Cat listesi yanlış olur.

 interface Animal {} interface Cat extends Animal { meow: () => string; } const duck = {age: 7}; const felix = { age: 12, meow: () => "Meow" }; const listOfAnimals: Animal[] = [duck]; const listOfCats: Cat[] = [felix]; function MyApp() { const [cats , setCats] = useState<Cat[]>(listOfCats); // Here the thing: listOfCats is declared as a Animal[] const [animals , setAnimals] = useState<Animal[]>(listOfCats) const [animal , setAnimal] = useState(duck) return <div onClick={()=>{ animals.unshift(animal) // we set as first cat a duck ! setAnimals([...animals]) // dirty forceUpdate } }> The first cat says {cats[0].meow()}</div>; }

TypeScript , jenerikler için yalnızca basit olan ve JavaScript geliştiricisinin benimsenmesine yardımcı olan iki değişkenli bir yaklaşıma sahiptir. Değişkenlerinizi doğru adlandırırsanız, nadiren listOfCats bir duck eklersiniz.

Ayrıca, kovaryans ve kontravaryans için sözleşmelerin eklenmesi ve çıkarılması için bir teklif vardır.

Çözüm

Kısa bir süre önce, iyi yazılmış bir dil olan Kotlin'e geri döndüm ve karmaşık jenerikleri doğru yazmak karmaşıktı. Ördek yazma ve iki değişkenli yaklaşımın basitliği nedeniyle TypeScript ile bu genel karmaşıklığa sahip değilsiniz, ancak başka sorunlarınız var. TypeScript ile ve TypeScript için tasarlanan Angular ile belki çok fazla sorunla karşılaşmadınız, ancak bunları React sınıflarında yaşadınız.

TypeScript muhtemelen 2019'un en büyük kazananıydı. React'i kazandı ama aynı zamanda Node.js ve oldukça basit bildirim dosyalarıyla eski kütüphaneleri yazma yeteneği ile arka uç dünyasını fethediyor. Bazıları ReasonML ile sonuna kadar gitse de, Flow'u gömüyor.

Şimdi, hooks+TypeScript'in Angular'dan daha keyifli ve üretken olduğunu hissediyorum. Altı ay önce böyle düşünmezdim.