Gerçek Bir Örnekle Gelişmiş TypeScript (Bölüm 1)

Yayınlanan: 2020-11-06

Geçen hafta TypeScript'e küçük bir giriş gördük ve özellikle JavaScript'i genişleten bu dilin daha sağlam kodlar oluşturmamıza nasıl yardımcı olabileceğinden bahsettik. Bu sadece bir giriş olduğundan, projelerinizde kullanmak isteyebileceğiniz (ve muhtemelen ihtiyaç duyabileceğiniz) bazı TypeScript özelliklerinden bahsetmedim.

Bugün size TypeScript'i gerçek bir projede profesyonel olarak nasıl uygulayacağınızı öğreteceğim. Bunu yapmak için, nereden başladığımızı ve şu anda hangi sınırlamalara sahip olduğumuzu anlamak için Nelio Content'in kaynak kodunun bir kısmına bakarak başlayacağız. Ardından, tam olarak yazılmış bir kod elde edene kadar küçük artımlı iyileştirmeler ekleyerek orijinal JavaScript kodunu kademeli olarak iyileştireceğiz.

Nelio Content'in kaynak kodunun temel olarak kullanılması

Bildiğiniz gibi Nelio Content, web sitenizin içeriğini sosyal medyada paylaşmanıza izin veren bir eklentidir. Buna ek olarak, yazılarınızın kaliteli bir analizi, yazmanız gereken yaklaşan içeriği takip etmek için bir editoryal takvim ve benzeri gibi blogunuzda sürekli olarak daha iyi içerik oluşturmanıza yardımcı olmayı amaçlayan çeşitli işlevler de içerir. .

Nelio Content'in editoryal takvimi
Nelio Content'in editoryal takvimi.

Geçen ay, eklentimizin hem görsel hem de dahili olarak tamamen yeniden tasarımı olan 2.0 sürümünü yayınladık. Bu sürümü, bir React arayüzü ve bir Redux mağazası da dahil olmak üzere, bugün WordPress'te mevcut olan tüm yeni teknolojileri (son zamanlarda blogumuzda bahsettiğimiz bir şey) kullanarak oluşturduk.

Bu nedenle, bugünün örneğinde ikincisini geliştireceğiz. Yani, bir Redux mağazasını nasıl yazabileceğimizi göreceğiz.

Nelio İçerik Editoryal Takvim Seçiciler

Editoryal takvim, haftanın her günü için planladığımız blog gönderilerini gösteren bir kullanıcı arayüzüdür. Bu, Redux mağazamızın en azından iki sorgu işlemine ihtiyaç duyacağı anlamına gelir: biri bize herhangi bir günde planlanan gönderileri söyler ve diğeri, bir gönderi kimliği verildiğinde tüm özelliklerini döndürür.

Konuyla ilgili yazılarımızı okuduğunuzu varsayarsak, Redux'daki bir seçicinin, ilk parametresi olarak uygulamamızın tüm bilgilerini ve ardından ihtiyaç duyabileceği ek parametreleri içeren durumu aldığını zaten biliyorsunuzdur. Yani JavaScript'teki iki örnek seçicimiz şuna benzer:

 function getPost( state, id ) { return state.posts[ id ]; } function getPostsInDay( state, day ) { return state.days[ day ] ?? []; }

Bir devletin posts ve days niteliklerine sahip olduğunu nasıl bildiğimi merak ediyorsanız, oldukça basit: çünkü onları tanımlayan benim. Ama işte bu yüzden onları bu şekilde uygulamaya karar verdim.

Bilgilerimize iki farklı bakış açısıyla erişmek istediğimizi biliyoruz: bir gün içindeki gönderiler veya kimlikle yapılan gönderiler. Dolayısıyla verilerimizi iki parça halinde düzenlemek mantıklı görünüyor:

  • Bir tarafta, sunucudan elde ettiğimiz ve Redux mağazamıza kaydettiğimiz tüm gönderileri listelediğimiz bir posts özelliğimiz var. Mantıksal olarak, onları bir diziye kaydedebilir ve kimliği beklenenle eşleşen gönderiyi bulmak için sıralı bir arama yapabilirdik… ancak bir nesne bir sözlük gibi davranır ve daha hızlı aramalar sunar.
  • Öte yandan, belirli bir gün için planlanan gönderilere de erişmemiz gerekiyor. Yine, tüm gönderileri depolamak ve belirli bir güne ait gönderileri bulmak için filtrelemek için tek bir dizi kullanabilirdik, ancak başka bir sözlüğe sahip olmak daha hızlı bir arama çözümü sunar.

Nelio İçeriğindeki Eylemler ve Azaltıcılar

Son olarak, dinamik bir takvim istiyorsak, mağazamızın depoladığı bilgileri güncellememize izin veren işlevleri uygulamamız gerekir. Basitlik için, iki basit yöntem önereceğiz: biri takvime yeni gönderiler eklememize izin veren ve diğeri mevcut olanların niteliklerini değiştirmemize izin veren.

Bir Redux mağazasında yapılan güncellemeler iki parça gerektirir. Bir yanda yapmak istediğimiz değişikliği bildiren aksiyonlarımız var, diğer yanda mağazamızın mevcut durumu ve güncelleme talep eden bir aksiyon verildiğinde, mevcut duruma gerekli değişiklikleri uygulayan bir redüktör var. yeni bir devlet oluştur.

Dolayısıyla, bunu dikkate alarak mağazamızda gerçekleştirebileceğimiz işlemler şunlardır:

 function receiveNewPost( post ) { return { type: 'RECEIVE_NEW_POST', post, }; } function updatePost( postId, attributes ) { return { type: 'UPDATE_POST', postId, attributes, } }

ve işte redüktör:

 function reducer( state, action ) { state = state ?? { posts: {}, days: {} }; const postIds = Object.keys( state.posts ); switch ( action.type ) { case 'RECEIVE_NEW_POST'; if ( postIds.includes( action.postId ) ) { return state; } return { posts: { ...state.posts, [ action.post.id ]: action.post, }, days: { ...state.days, [ action.post.day ]: [ ...state.days[ action.post.day ], action.post.id, ], }, }; case 'UPDATE_POST'; if ( ! postIds.includes( action.postId ) ) { return state; } const post = { ...state.posts[ action.postId ], ...action.attributes, }; return { posts: { ...state.posts, [ post.id ]: post, }, days: { ...Object.keys( state.days ).reduce( ( acc, day ) => ( { ...acc, [ day ]: state.days[ day ].filter( ( postId ) => postId !== post.id ), } ), {} ), [ post.day ]: [ ...state.days[ post.day ], post.id, ], }, }; } return state; }

Her şeyi anlamak için zaman ayırın ve ilerleyelim!

JavaScript'ten TypeScript'e

Yapmamız gereken ilk şey, önceki kodu TypeScript'e çevirmek. Pekala, TypeScript JavaScript'in bir üst kümesi olduğundan, zaten öyledir... ama önceki işlevleri kopyalayıp TypeScript Playground'a yapıştırırsanız, örtük türü any olan çok fazla değişken olduğundan derleyicinin biraz şikayet ettiğini göreceksiniz. Öyleyse, önce bazı temel türleri açıkça ekleyerek bunu düzeltelim.

Tek yapmamız gereken, any şeye (uygulamamızın durumu gibi) eklemek ve number veya string veya başka herhangi bir değişkene/argümana ne istersek onu kullanmaktır. Örneğin, orijinal JavaScript seçicisi:

 function getPost( state, id ) { return state.posts[ id ]; }

açık TypeScript türleri ile şöyle görünür:

 function getPost( state: any, id: number ): any | undefined { return state.posts[ id ]; }

Gördüğünüz gibi, kodumuzu yazmanın basit eylemi (“jenerik türler” kullandığımızda bile), hızlı bir bakışla birçok bilgi sunar; temel JavaScript'e kıyasla net bir gelişme! Bu durumda, örneğin, getPost bir number beklediğini (bir gönderi kimliği bir tamsayıdır, hatırladınız mı?) ve sonucun gönderi varsa bir şey ( any ) veya yoksa hiçbir şey ( undefined ) olacağını görüyoruz.

Burada, derleyicinin şikayet etmemesi için basit türleri kullanan tüm kod türleriyle bağlantınız var.

TypeScript'te Özel Veri Türleri Oluşturun ve Kullanın

Derleyici kaynak kodumuzdan memnun olduğuna göre, şimdi onu nasıl geliştirebileceğimizi düşünmenin zamanı geldi. Bunun için her zaman alanımızda sahip olduğumuz kavramları modelleyerek başlamayı öneriyorum.

Gönderiler için Özel Bir Tür Oluşturma

Mağazamızın öncelikle gönderiler içereceğini biliyoruz, bu nedenle ilk adımın bir gönderinin ne olduğunu ve onunla ilgili hangi bilgilere sahip olduğumuzu modellemek olduğunu savunuyorum. Geçen hafta özel türlerin nasıl oluşturulacağını zaten gördük, bu yüzden bugün posta konseptiyle bir şans verelim:

 type Post = { id: number; title: string; author: string; day: string; status: string; isSticky: boolean; };

Burada sürpriz yok, değil mi? Post , sayısal id , metin title vb. gibi birkaç özniteliği olan bir nesnedir.

Herhangi bir Redux mağazasının sahip olduğu bir diğer önemli bilgi parçası, tahmin ettiğiniz gibi durumudur. Önceki bölümde sahip olduğu nitelikleri tartışmıştık, o halde State türümüzün temel şeklini tanımlayalım:

 type State = { posts: any; days: any; };

State Tipinin İyileştirilmesi

Artık State öğesinin iki özelliği olduğunu biliyoruz ( posts ve days ), ancak any şey olabileceğinden, bunların ne olduğu hakkında fazla bir şey bilmiyoruz. Her iki özelliğin de sözlük olmasını istediğimizi söyledik. Yani, belirli bir sorgu verildiğinde (ya gönderiler için bir posts kimliği veya days için bir tarih), ilgili verileri (sırasıyla bir gönderi veya gönderi listesi) isteriz. Bir nesneyi kullanarak bir sözlük uygulayabileceğimizi biliyoruz, ancak bir sözlüğü TypeScript'te nasıl temsil ederiz?

TypeScript belgelerine bir göz atarsak, oldukça yaygın durumlarla başa çıkmak için birkaç yardımcı program türü içerdiğini göreceğiz. Spesifik olarak, istediğimiz gibi görünen Record adında bir tür var: anahtarın belirli bir Keys türüne sahip olduğu ve değerlerin Type türünde olduğu anahtar/değer çiftlerini kullanarak bir değişken yazmamıza izin veriyor. Örneğimize bu türü uygularsak, şöyle bir şey elde ederiz:

 type State = { posts: Record<number, Post>; days: Record<string, number[]>; };

Derleyicinin bakış açısından, Record türü, herhangi bir Keys değeri verildiğinde (örneğimizde, posts için number ve days için string ), sonucu her zaman Type türünde bir nesne olacak şekilde çalışır (bizim durumumuzda, bir Sırasıyla Post veya bir number[] ). Sorun şu ki, sözlüğümüzün böyle davranmasını istemiyoruz: kimliğini kullanarak belirli bir gönderi aradığımızda, derleyicinin ilgili bir gönderi bulabileceğimizi veya bulamayacağımızı bilmesini istiyoruz, bu da sonucun bir Post olabileceği anlamına geliyor. veya undefined .

Neyse ki, bunu başka bir yardımcı program türü olan Partial tür kullanarak kolayca düzeltebiliriz:

 type State = { posts: Partial< Record<number, Post> >; days: Partial< Record<string, number[]> >; };

Tür Takma Adları ile Kodumuzu Geliştirme

Bizim durumumuzda bulunan posts özniteliğine bir göz atın… Ne görüyorsunuz? Post tipi gönderileri sayılarla indeksleyen bir sözlük, değil mi? Şimdi kendinizi iş yerinde bu kodu incelerken hayal edin. Eğer böyle bir number karşılaşırsanız, indeksleme gönderilerinin muhtemelen indekslenmiş gönderilerin ID'si olduğunu varsayabilirsiniz… ama bu sadece bir varsayımdır; Emin olmak için kodu gözden geçirmeniz gerekir. Peki ya days ? "Rastgele diziler, sayı listelerini indeksliyor." Bu pek yardımcı olmadı, değil mi?

TypeScript türleri, derleyici kontrolleri sayesinde daha sağlam kod yazmamıza yardımcı olur, ancak bundan çok daha fazlasını sunarlar. Anlamlı türler kullanırsanız, kodunuz daha iyi belgelenecek ve bakımı daha kolay olacaktır. Öyleyse anlamlı türler oluşturmak için mevcut türlere takma ad verelim, olur mu?

Örneğin, posta kimliklerinin ( number ) ve tarihlerin ( string ) etki alanımızla alakalı olduğunu bilerek, aşağıdaki tür takma adlarını kolayca oluşturabiliriz:

 type PostId = number; type Day = string;

ve ardından bu takma adları kullanarak orijinal türlerimizi yeniden yazın:

 type Post = { id: PostId; title: string; author: string; day: Day; status: string; isSticky: boolean; }; type State = { posts: Partial< Record<PostId, Post> >; days: Partial< Record<Day, PostId[]> >; };

Kodumuzun okunabilirliğini geliştirmek için kullanabileceğimiz diğer bir takma ad türü, Partial ve Record kullanmanın karmaşıklığını uygun bir yapının arkasına "gizleyen" Dictionary türüdür:

 type Dictionary<K extends string | number, T> = Partial< Record<K, T> >;

kaynak kodumuzu daha net hale getirmek:

 type State = { posts: Dictionary<PostId, Post>; days: Dictionary<Day, PostId[]>; };

Ve bu kadar! İşte aldın! Yalnızca üç basit tür takma adla, kodu, yorum kullanmaktan açıkça daha iyi bir şekilde belgeleyebildik. Bizden sonra gelen herhangi bir geliştirici, bir bakışta, posts , PostId kullanarak Post türündeki nesneleri dizine ekleyen bir sözlük olduğunu ve o days , bir Day verildiğinde, bir liste gönderi tanımlayıcılarını döndüren bir veri yapısı olduğunu bilebilecek. Bana sorarsan bu oldukça harika.

Ancak, yalnızca tür tanımlarının kendisi daha iyi değildir… tüm kodumuzda bu yeni türleri kullanırsak:

 function getPost( state: State, id: PostId ): Post | undefined { return state.posts[ id ]; }

aynı zamanda bu yeni anlam katmanından da yararlanır! Yazdığımız kodun yeni halini buradan görebilirsiniz.

Bu arada, tür takma adlarının derleyicinin bakış açısından "orijinal" türden ayırt edilemez olduğunu unutmayın. Bu, örneğin bir PostId ve bir number tamamen değiştirilebileceği anlamına gelir. Bu nedenle, bir number bir PostId veya bunun tersini yaparsanız (bu küçük örnekte görebileceğiniz gibi) derleyicinin bir hata tetiklemesini beklemeyin; sadece kaynak kodumuza anlambilim eklemeye hizmet ederler.

Sonraki adımlar

Gördüğünüz gibi, TypeScript türlerini aşamalı olarak kullanarak JavaScript kodunu yazabilirsiniz ve bunu yaparken kalitesi ve okunabilirliği artar. Bugünkü gönderide, bir React + Redux uygulamasının gerçek bir uygulamasının bir örneğini biraz ayrıntılı olarak gördük ve nispeten az çabayla nasıl geliştirilebileceğini gördük. Ama daha gidecek çok yolumuz var.

Bir sonraki gönderide, şu anda any türü kullanan kalan tüm değişkenleri/argümanları yazacağız ve ayrıca bazı gelişmiş TypeScript özelliklerini öğreneceğiz. Umarım bu ilk bölümü beğenmişsinizdir ve beğendiyseniz lütfen arkadaşlarınızla ve iş arkadaşlarınızla paylaşın.

Unsplash'ta Danielle MacInnes tarafından öne çıkan görsel.