Kısım II · İnşaat · Bölüm 30
TypeScript: Tiplerin güvencesi
JavaScript'in üstüne yazılan denetlenebilir sözleşmeler (TypeScript)
Neden burada: Önce JavaScript ile davranışı yazdık, sonra dosyalara böldük, ardından paketleri ve araç zincirini tanıdık. Şimdi dosyalar birbirine veri verirken bir soruyu erkenden soracağız: “Bu fonksiyonun beklediği şey gerçekten bu mu?” TypeScript bu soruyu programı çalıştırmadan denetlememizi sağlar. Bir sonraki bölümde React bileşenlerinin girdilerini, ardından Expo ekranlarını ve TanStack ile gelen verinin beklenen şeklini bu dille okuyacağız.1
Neye dayanıyor: Bölüm 21.3: statik ve dinamik tipler, tip çıkarımı; burada o ayrımı kullanacağız. Bölüm 25: sayı, karakter dizisi, nesne, dizi, fonksiyon ve Promise; yeni bir davranış dili kurmuyoruz. Bölüm 29.3: dönüştürücü ile paketleyici ayrımı; burada dönüştürmek ile tip denetlemek arasına da çizgi çekeceğiz. Bölüm 27.1'deki puanHesapla fonksiyonuna sonunda tip ekleyeceğiz.
- Kat 10Bizim uygulamamızSoru, puan, seri ve can verilerinin beklenen şekilleri.
- Kat 9Web ve tarayıcı: HTML, CSS, JavaScriptWeb'de çalışacak çıktı JavaScript'tir; telefon tarafındaki karşılığı Bölüm 32'de.
- Kat 6–8Açılış, işletim sistemi ve ağDosyaları ve araç süreçlerini taşıyan, önceki bölümlerde kurduğumuz altyapı.
- Kat 5Programlama dilleri ve derleyiciTypeScript, JavaScript kaynak koduna çalışmadan önce denetlenen bir tip katmanı ekler.
- Kat 0–4Elektrikten işlemciye ve makine dilineTypeScript yeni donanım değildir; çalışan JavaScript yine bu temelin üstündedir.
Bu bölümün sonunda : number, type Soru, interface Kullanici, number | null ve Array<Soru> gördüğünde ne okuduğunu söyleyebileceksin. Daha önemlisi, “tip denetiminden geçti” sözünün neyi güvence altına almadığını bileceksin. Amacımız bütün TypeScript sözdizimini ezberlemek değil; agent'ın yazdığı bir sözleşmeyi okuyup “neyi bekliyor, nerede denetleniyor, nerede yalnızca varsayılıyor?” sorularını sorabilmek.
30.1 Neden: hatayı programı çalıştırmadan yakalamak; tip bir sözleşmedir
Bölüm 25.1'de sayı 10 ile karakter dizisi "10"'un farklı değerler olduğunu gördün. Küçük bir JavaScript örneğinde bu farkı unutalım:
function puanEkle(puan, kazanilan) {
return puan + kazanilan;
}
console.log(puanEkle(70, "10"));
İlk satır iki parametre alan fonksiyonu tanımlar; ikinci satır + işleminin sonucunu döndürür; son satır fonksiyonu sayı ve metinle çağırıp sonucu yazar. Sonuç sayı 80 değil, karakter dizisi "7010"'dur. Bu JavaScript açısından geçerli bir işlemdir: metin birleştirilmiştir. Uygulamanın puan kuralı açısından yanlıştır. TypeScript JavaScript'in üstüne, bu tür uyuşmazlıkları kaynak kodda ifade edip denetleyebildiğin bir tip sistemi (type system) koyar; davranışın kendisini başka bir toplama kuralıyla değiştirmez.25
function puanEkle(puan: number, kazanilan: number): number {
return puan + kazanilan;
}
puanEkle(70, "10"); // Bilerek hatalı çağrı
Burada puan: number ilk parametrenin sayı olmasını ister; kazanilan: number ikincisine aynı koşulu koyar; parantezden sonraki : number dönüş değerinin de sayı olacağını bildirir. Bir adın ardından tipi açıkça yazmaya tip belirtimi (type annotation) diyeceğiz. Son satırdaki metin, ikinci parametrenin sözleşmesine uymaz. Derleyici bunu fonksiyonu çalıştırmadan bildirir. “Metin geldi, ben onu sayıya çevireyim” demez; “burada sayı bekleniyordu” der.3
Buna statik tip denetimi (static type-checking) denir: “statik”, programı bu denetim için çalıştırmadan incelemek demektir. Denetleyici hangi fonksiyona hangi değerin verildiğini, nesnede beklenen özelliğin bulunup bulunmadığını ve bir işlemin eldeki tip için anlamlı olup olmadığını kaynak üzerinden değerlendirir. Editördeki tamamlamalar da aynı bilgiden yararlanır: soru. yazınca bilinen özellikleri önermek, yalnızca harf tahmin etmek değildir.15
Sözleşme benzetmesinin yararı ve sınırı
Hukuktaki bir sözleşme gibi düşünmek şu noktada işe yarar: taraflardan biri “sayı ver”, diğeri “sayı döndür” sözünü açıkça yazar. Ama benzetmeyi burada durdur. TypeScript dışarıdan gelecek her gerçek olayı önceden görmez; kendisine verilen tip bilgileri ve kodun yapısı üzerinden çalışır. Bir tip belirtimi, kullanıcı cihazına yerleştirilmiş sürekli bir zabıta değildir. 30.3'te bu belirtimlerin çıktıda silindiğini göreceğiz.5
number, tek başına “negatif olmayan tam puan” demek değildir. puanEkle(70, -10) iki sayı aldığı için bu sözleşmeye uyar; uygulama kuralı puan eksiltmeyi yasaklıyorsa o kuralı ayrıca yazmalısın. İki sayı parametresinin yerini karıştırman da tip hatası oluşturmak zorunda değildir. Bu bölümdeki güvencenin anlamı “bildirdiğimiz tip ilişkileri denetlendi”dir; “ürünün bütün kuralları doğru” değildir. Testlerin ayrı rolüne Bölüm 42'de döneceğiz.35
30.2 Temel tipler, arayüz, tip takma adı, birleşim tipi
Bildik değerlerin üstüne ad yazmak
JavaScript değerlerini baştan anlatmayacağız; 25.1'deki değerlere TypeScript'in hangi adlarla baktığını göstereceğiz. Sık kullanılan üç ilkel tip string, number ve boolean'dır. Küçük harfle yazılırlar. String ile string aynı yazım değildir; burada büyük harfli nesne sarmalayıcılarını kullanmıyoruz. number için C'deki gibi ayrı int ve float yazmayız.3
| Yazım | Ne anlatır? | Örnek değer |
|---|---|---|
string | Karakter dizisi, gündelik dille metin | "Kerem" |
number | Sayı | 70 |
boolean | Doğru veya yanlış | true |
string[] | Elemanlarının tipi karakter dizisi olan dizi | ["A", "B"] |
null / undefined | İki farklı yokluk değerinin tipleri | null / undefined |
Tablodaki string[], string'in arkasına iki köşeli parantez koyar: “bu tipten elemanlar taşıyan dizi.” Dizi hakkında ne bildiğimizi yazar, belleğe yeni bir dizi yerleştirmez. null ve undefined'ın ayrı ele alınması, birazdan kullanacağımız sıkı denetim ayarına bağlıdır.3
const ad: string = "Kerem";
let puan = 70;
const tamamlandi: boolean = false;
const sikler: string[] = ["İcap", "Kabul"];
puan = 80;
İlk satır adın metin olduğunu açıkça söyler. İkinci satırda : number yazmadık; başlangıç değerinden tip çıkarımı (type inference) yapılır ve puan sayı olarak izlenir. Üçüncü satır doğru/yanlış bilgisini, dördüncü satır metin dizisini tanımlar. Son satır başka bir sayı atar; geçerlidir. Onun yerine puan = "seksen" yazmak tip uyuşmazlığı olur. Her satıra tip eklemek zorunda değilsin; açık belirtim özellikle modüllerin ve fonksiyonların sınırlarını anlaşılır kılar.23
Nesnenin sözleşmesi: arayüz
Bir kullanıcı adı yalnızca metin olabilir; fakat kullanıcı kaydı birkaç özelliği bir arada taşır. Arayüz (interface) bildirimi, böyle bir nesnenin beklenen şeklini adlandırır. Bölüm 1.4'teki genel arayüz fikrinin dildeki somut biçimlerinden biridir; kullanıcı arayüzü, yani ekrandaki düğmeler anlamına gelmez.23
interface Kullanici {
id: number;
ad: string;
puan: number;
takmaAd?: string;
}
const kerem: Kullanici = { id: 1, ad: "Kerem", puan: 70 };
interface Kullanici bir tip tanımlar; kullanıcı nesnesi üretmez. İçerideki ilk üç özellik zorunludur. takmaAd? ise isteğe bağlı özellik (optional property): kayıt bu özelliği taşımayabilir. Son satırda gerçek bir JavaScript nesnesi oluşturulur ve şekli Kullanici ile karşılaştırılır. puan'ı çıkarmak ya da ona "70" vermek hata oluşturur. İsteğe bağlı özelliği okurken yok olma ihtimalini ele almak gerekir; ?, “yoksa otomatik metin üret” talimatı değildir.3
TypeScript burada öncelikle nesnenin adına değil yapısına bakar; buna yapısal tipleme (structural typing) denir. Bir fonksiyon Kullanici bekliyorsa, gereken özellikleri uygun tiplerle taşıyan bir değişken ona verilebilir; o nesnenin oluşturulurken mutlaka Kullanici etiketiyle bildirilmiş olması gerekmez. Bu “her ek özellik her bağlamda serbest” demek de değildir: doğrudan verilen nesne yazımlarında fazladan özellik denetimleri bulunur. Başlangıç için alacağın fikir şu: arayüz bir sınıfın nüfus kâğıdından çok, gerekli özelliklerin listesidir.23
Tip takma adı: karmaşık tarife kısa ad
Tip takma adı (type alias), bir tipe type ile ad vermektir. Nesne şekillerini de adlandırabilir; yalnızca onlarla sınırlı değildir. Aynı soru tarifini farklı fonksiyonların içine tekrar tekrar yazmak yerine bir kez adlandırırız:3
type Soru = {
id: number;
metin: string;
sikler: string[];
dogru: number;
};
type CevapBildir = (kazanilan: number) => void;
Soru'nun ilk satırı sayısal kimlik, ikincisi soru metni, üçüncüsü metinlerden oluşan şık dizisi, dördüncüsü doğru şıkkın indeksidir. dogru: number indeksin dizinin sınırları içinde olduğunu tek başına kanıtlamaz; bunun ayrı bir kural olduğunu unutma. Son satır bir fonksiyon tipini adlandırır: sayı alan, çağıranın kullanacağı bir dönüş değeri beklenmeyen fonksiyon. Buradaki void bu dönüş beklentisini anlatır. => bu konumda fonksiyonun gövdesi değil, girdi ile dönüş tipi arasındaki işarettir. Bir sonraki bölümde onCevap: (kazanilan: number) => void gördüğünde, “buraya çağrılabilir bir fonksiyon verilecek” diye okuyacaksın.23
interface ile type nesne şekli yazarken çoğu zaman benzer işi yapar; biri iyi, diğeri kötü değildir. Önemli ayrım: arayüz bildirimleri uygun koşullarda aynı ad altında genişletilip birleştirilebilir; tip takma adı aynı adla yeniden açılıp böyle genişletilmez. Birleşim tiplerine ad vermek için ise type kullanacağız. Ayrıca type Puan = number yeni, diğer sayılardan kesin ayrılan bir puan tipi üretmez; yalnızca number'a başka ad verir. Takma adın adı tek başına negatif sayıları ya da yanlış anlamdaki sayıları yasaklamaz.3
Birleşim tipi: seçeneklerden biri
Birleşim tipi (union type), bir değerin belirtilen tiplerden biri olabileceğini söyler. Aradaki | işaretini “veya” diye oku. number | null, “sayı veya bilerek boş bırakılmış değer”; string | number, “metin veya sayı” demektir. İkincisi bir metni sayıya dönüştürmez, iki olasılığı da sözleşmeye kabul eder. Olasılıklar daraltılmadan yalnızca hepsi için geçerli işlemler yapılabilir.3
function secimiYaz(secili: number | null): string {
if (secili === null) return "Henüz şık seçilmedi";
return secili.toFixed(0);
}
İlk satır boş seçim ihtimalini açıkça taşır. İkinci satır bu ihtimali denetler ve boşsa fonksiyondan çıkar. Üçüncü satıra ulaşılmışsa değer artık sayı olabilir, null olamaz; bu nedenle sayıların metotlarından toFixed kullanılabilir. TypeScript'in kodun yollarını izleyerek olasılıkları azaltmasına tip daraltma (narrowing) denir. Burada kontrolü gerçekten yapan şey yine JavaScript'in if'idir; TypeScript bu kontrolden bilgi çıkarır.4
Benzer biçimde string | number olan bir değerde typeof deger === "string" kontrolü, o dalda metin işlemlerine izin verir. typeof çalışma sırasında temel tip bilgisi veren bir JavaScript işlecidir; yeni bir TypeScript denetim motoru değildir. Dikkat: typeof null sonucu "object" olduğu için yalnızca “nesne mi?” sorusuyla null olasılığı elenmez. Boş seçimi sınarken if (secili) de yanlış sezgi verebilir: geçerli ilk indeks 0 koşulda yanlış sayılır. secili === null tam olarak aradığın yokluğu sorar.4
Birleşimin üyeleri belirli değerler de olabilir. type CevapDurumu = "bekliyor" | "dogru" | "yanlis"; yalnızca bu üç metne izin verir. Bunlar sabit değer tipleri (literal types) ile yazılmıştır: genel string yerine tek tek izinli değerler. Böylece "dogur" gibi bir yazım yanlışı sözleşmeye uymaz. Herhangi bir metin kabul etmekten daha dar bir tarif yazmış olduk.3
Gerçek prototip kodunu görmedik; fakat bir soru ekranının beklemesi gereken iki ayrı durum var: seçim yapılmadı ve ilk şık seçildi. Örneğimizde bunları null ve 0 ile ayırırız. Bölüm 31.2'deki useState<number | null>(null) tam bunu kullanır. Şimdilik useState'in nasıl çalıştığını öğrenmen gerekmiyor; köşeli açılardan okunacak tipi 30.4'te kuracağız.
30.3 Tip denetimi ve derleme: tsc; tiplerin çalışma anında silinmesi
Önce denetlemek, sonra çalıştırmak
TypeScript paketinin komut satırı aracı TypeScript derleyicisi (TypeScript compiler, tsc) adını taşır. Bölüm 9.1'de derleyici C'den makine koduna giden yolun parçasıydı; burada tsc tip denetleyebilir ve JavaScript çıktısı üretebilir. “Derleyici” adı her zaman doğrudan işlemciye giden dosya demek değildir. Üretilen JavaScript'i çalıştırmak daha sonra JavaScript çalışma ortamının işidir.5
Aşağıdaki küçük deney, Node.js ve npm hazır olan ayrı bir deneme klasöründe yapılabilir. Prototipin üstüne kurulum yapman gerekmiyor. İlk komut TypeScript'i geliştirme bağımlılığı olarak kurar; ikincideki npx kurulan paketin komutunu çağırır. Paket ve geliştirme bağımlılığı kavramlarını 29.1'de kurduk.5
npm install --save-dev typescript
npx tsc --strict --noEmit demo.ts
İkinci komut demo.ts'yi denetler. --strict sıkı tip denetimi seçenekleri grubunu açar; özellikle örtük any sorunları ve olası null/undefined erişimleri daha görünür olur. --noEmit çıktı dosyası yazmayı kapatır, denetimi kapatmaz. Hata bulunmadığında komutun sessizce bitmesi normaldir. Bir önceki alt bölümdeki yokluk örneklerini bu sıkı ayarla okuyoruz.58
demo.ts'ye şu bağımsız örneği yazalım:
function puanEkle(puan: number, kazanilan: number): number {
return puan + kazanilan;
}
console.log(puanEkle(70, 10));
npx tsc --strict --target ES2020 --outDir cikti demo.ts
node cikti/demo.js
İlk komut tip denetler ve çıktıyı cikti klasörüne yazar; --target ES2020, üretilecek JavaScript'in dil hedefini belirtir. İkinci komut o JavaScript'i Node ile çalıştırır. Yazarın bu örnekle aldığı sonuç 80'dir; aşağıdaki, üretilen dosyanın içeriğidir. Sayısal beklenti ayrıca Python ile hesaplandı.5
"use strict";
function puanEkle(puan, kazanilan) {
return puan + kazanilan;
}
console.log(puanEkle(70, 10));
Tiplerin silinmesi ne demek?
Parametrelerdeki ve dönüş değerindeki : number çıktıdan kayboldu. Buna tiplerin silinmesi (type erasure) denir. Tip belirtimleri, interface ve type gibi yalnızca tip anlatan bildirimler çalışma kodu değildir; JavaScript çıktısında bulunmazlar. Zaman sırası önemlidir: tipler derleme sırasında silinir; program çalışırken bu tip belirtimleri artık yoktur. JavaScript'in sayı, metin ve nesne gibi kendi çalışma zamanı değerleri ise elbette vardır. Silinen şey verinin kendisi ya da JavaScript'in bütün tip davranışları değildir.35
“TypeScript'te yazdığım her şey silinir” sonucu da yanlış olur. if, toplama, fonksiyon çağrısı gibi gerçek davranışlar kalır; bazı TypeScript özellikleri de JavaScript kodu üretir. Örneğin enum yalnızca tip düzeyinde bir bildirim değildir; bu bölümde onu kullanmıyoruz. Silinme kuralını özellikle tip belirtimleri ve tip bildirimleri için aklında tut. Tarayıcıya .ts dosyasını düz bir JavaScript betiği gibi verip içindeki : number'ı anlamasını bekleme; araç zincirinin dönüştürme işi bunun içindir.35
| İş | Hangi soruyu yanıtlar? | Tek başına neyi söylemez? |
|---|---|---|
| Tip denetimi | Kaynak koddaki tip ilişkileri tutarlı mı? | Ürünün her davranışı doğru mu? |
| TypeScript'i JavaScript'e dönüştürme | Çalışma ortamına hangi kod verilecek? | Tip denetimi ayrıca çalıştırıldı mı? |
| Programı çalıştırma | Bu gerçek girdide ne oluyor? | Bütün olası girdiler doğru ele alındı mı? |
tsc ilk iki işi bir arada yapabilir; --noEmit ile yalnızca ilkini yapmasını isteyebilirsin. Başka bir dönüştürücü tipleri kaldırırken tip denetimini başka adıma bırakabilir. Bu yüzden “ekran açıldı” ile “tip denetiminden geçti” aynı haber değildir. 29.2'deki derleme betiğinin önce tsc çağırması bu ayrımı görünür kılar.58
Bir ayrıntı daha: tsc'nin tip hatası bildirmesi, her ayarda JavaScript çıktısını engellediği anlamına gelmez. Hata varken yeni çıktı üretmesini istemiyorsan --noEmitOnError kullanılır. Bu, --noEmit'ten farklıdır: biri hatalı derlemede çıktı yazmayı, diğeri hata olsa da olmasa da bütün çıktı yazmayı kapatır. “Dosya üretildi, demek ki tipler doğru” diye karar verme; komutun başarıyla bitip bitmediğine bak.5
Dışarıdan gelen veri: belirtmek doğrulamak değildir
Bir JSON metnini JSON.parse ile ayrıştırmak, metnin JSON kurallarına uyup uymadığını denetler ve bir JavaScript değeri üretir. “Bu değer benim uygulamamın puan kaydı mı?” farklı bir sorudur. Tip iddiası (type assertion) olan as, derleyiciye elinde ek bilgi olduğunu söylemektir; çalışma sırasında veriyi incelemez ya da dönüştürmez.310
type PuanKaydi = { puan: number };
const kayit = JSON.parse('{"puan":"70"}') as PuanKaydi;
console.log(kayit.puan + 10);
İlk satır beklenen şekli tanımlar. İkincisi metni ayrıştırır ama as PuanKaydi ile o şekli iddia eder. Üçüncü satır denetimden geçebilmesine rağmen çalışınca "7010" yazar: kaydın içindeki değer hâlâ metindir. Tip iddiası sayıya çevirme değildir. Benzer biçimde bir değerin sonuna konan !, null/undefined ihtimalini denetlemeden yok saydıran bir tip iddiasıdır; kayıp öğeyi bulmaz. Agent uyarıyı as veya ! ekleyerek susturduysa hangi gerçek bilgiye dayandığını sor.3
Burada iki özel tip ayrımını bilmek yararlı. any, ilgili değer üzerinde tip denetiminin önemli kısmını devreden çıkarır; bilinmeyen özelliklere erişmene izin verir. unknown ise “henüz tipini bilmiyorum” der ve kullanmadan önce daraltmanı ister. Dış veriyi unknown olarak karşılayıp gerçekten kontrol etmek, beklenen şekli yalnızca ilan etmekten farklıdır.234
function puaniOku(veri: unknown): number {
if (typeof veri !== "object" || veri === null) {
throw new Error("Puan kaydı bekleniyordu");
}
if (!("puan" in veri) || typeof veri.puan !== "number") {
throw new Error("Puan sayı olmalı");
}
return veri.puan;
}
İlk satır, henüz bilinmeyen bir girdi alır. İlk koşul nesne olmayanı ve null'ı reddeder. İkinci koşul “puan özelliği var mı, değeri sayı mı?” diye sorar; || kısa devre yaptığı için özellik yoksa sağdaki erişim değerlendirilmez. throw hata fırlatır ve o yürütmeyi keser. Son satıra gelindiğinde özellik sayı olarak daraltılmıştır. Kontroller JavaScript çıktısında kalır: bu kez gerçek veri üzerinde doğrulama (validation) yapıyoruz. Bu örnek yalnızca nesne/özellik/sayı biçimini doğrular; negatif olmayan tam puan gibi ek ürün kurallarını doğruladığı iddiasında değiliz.4
Promise<Soru[]> yazmak, ağdan gelen soruların alanlarını kendi başına doğrulamaz. Bölüm 33.2'de veri getirme fonksiyonunun dönüş tipini okurken bunu hatırla: beklenen veri şeklini bildirmek ile dışarıdan gelen gerçek değerin o şekle uyduğunu sınamak iki ayrı iştir. JSON ayrıştırma Bölüm 35.3'te, sunucudaki doğrulama ise Bölüm 37.2'de ayrıntılanacak.310
30.4 Genel tipler: aynı ilişkiyi farklı tiplerle korumak
Şimdiye kadar belirli tipler yazdık: sayı, soru, kullanıcı. Fakat aynı davranış farklı tiplerle çalışabilir. Bir dizinin ilk elemanını okumak hem soru dizisinde hem metin dizisinde aynı iştir. İki ayrı fonksiyon yazmak tekrardır; giriş ve çıkışı any yapmak ise “metin dizisi verdim, ne aldım?” bilgisini kaybettirir. Genel tipler (generics), bu ilişkinin tipini bir parametreyle ifade etmemizi sağlar.6
function ilk<T>(liste: T[]): T | undefined {
return liste[0];
}
const ilkMetin = ilk(["İcap", "Kabul"]);
const ilkPuan = ilk<number>([70, 80]);
<T> bir tip parametresi (type parameter) tanımlar. T özel bir hazır tip adı değildir; yer tutucunun adı olarak Eleman da yazabilirdik. liste: T[] “bu çağrıda seçilen tipten elemanların dizisi”, T | undefined “o eleman tipinden değer veya boş dizide bulunamayan değer” demektir. İkinci satır sıfırıncı elemanı döndürür. Üçüncü satırda T metin olarak çıkarılır; son satırdaki <number> ise tipi açıkça seçer. Sonuç tipleri sırasıyla string | undefined ve number | undefined'dır. Dönüşteki yokluk ihtimalini özellikle yazdık; “ilk eleman” ile “mutlaka eleman var” aynı vaat değildir.36
Genel fonksiyonun içinde de sözleşme geçerlidir: T'nin ne olduğunu bilmiyorsan keyfine göre liste[0].toUpperCase() çağıramazsın; eleman sayı da olabilir. Genel tip, denetimden kaçış değil, giriş ile çıkış arasında korunacak ilişkinin tarifidir. any “ilişkiyi denetleme” derken T “tip hangisi seçilirse seçilsin bu ilişkiyi koru” der.6
Daha önce gördüğün köşeli açılar
Array<Soru> ile Soru[] aynı dizi tipini anlatır. Promise<Soru[]> ise 25.6'daki Promise'in başarıyla sonuçlandığında sağlayacağı değerin soru dizisi olmasını tarif eder; başarısız olma ihtimalini ortadan kaldırmaz. Böylece yeni bir çalışma zamanı mekanizması öğrenmeden tanıdık nesnelerin taşıdığı bilgiye tip verebiliriz.36
type ListeSonucu<T> = { kayitlar: T[] };
const sorular: ListeSonucu<Soru> = { kayitlar: [] };
İlk satır, eleman tipi dışarıdan seçilen bir nesne tipine ad verir. İkinci satırda o boşluğu Soru ile doldurduk: sorular.kayitlar artık soru dizisidir; boş dizi de geçerli bir soru dizisidir. Bu parça 30.2'de tanımlanan Soru tipini kullanır. Genel nesne tipleri ve genel fonksiyonlar aynı “tipi parametreleştir” fikrinin iki görünümüdür.6
React bölümündeki useState<number | null>(null) ifadesinde açılı parantez “hangi tipte durum tutulacak?”, normal parantez “başlangıç değeri ne?” sorusunu cevaplar. Expo örneğindeki useLocalSearchParams<{ id: string }>() benzer biçimde beklenen parametre şeklini bildirir. Şimdilik bu fonksiyonların nasıl gerçeklendiğini öğrenmiyoruz; 31.4 ve 32.3 onların yeridir. Açılı paranteze bakıp “HTML etiketi açıldı” sanmamak yeterli.
30.5 JavaScript prototipinden TypeScript'e kademeli geçiş
TypeScript'e geçiş “HTML, CSS ve JavaScript'i atıp başka bir ürün yazmak” değildir. Mevcut JavaScript davranışlarını ve modül düzenini koruyup denetimi adım adım artırabiliriz. Resmî geçiş rehberi JavaScript dosyalarının kabul edilmesini, kaynak ile çıktının ayrılmasını, ardından dosyaların tek tek .ts'ye taşınmasını anlatır. Kerem'in prototip kodunu görmediğimiz için aşağıdaki bir gerçek dosya dökümü değil, önerilen çalışma sırasıdır.7
- Çalışan davranışı tarif et. Örneğimizde yanlış cevap sıfır puan, doğru cevap temel puan, belirli seride bonus alır. Neyi koruduğunu bilmiyorsan uzantı değişikliğiyle davranış değişikliğini karıştırırsın. Bu bir çalışma önerisidir; TypeScript'in otomatik verdiği bir güvence değildir.
- Önce küçük bir JavaScript dosyasını denetle.
// @ts-checkile tek dosyada denetim açabilir, yorumlardaki JSDoc belirtimleriyle parametreleri anlatabilirsin. Her dosyayı aynı gün dönüştürmek zorunda değilsin.89 - Bir küçük modülü .ts yap. DOM'a dokunmayan puan hesabı iyi bir örnektir; sayı, koşul ve dönüş değerinden oluşur. Eksik tip bilgisini tamamla, gerçek hatayı düzelt; her uyarıyı
anyile susturma.7 - Verinin ortak şekillerini adlandır.
Soru,Kullanici, seçili şık gibi sınırları tarif et; sonra bu veriyi kullanan dosyaları sırayla denetle..jsile.tsgeçiş süresince birlikte bulunabilir.78 - Dönüştürme ve denetleme adımlarını görünür tut. Kaynak
.tsdosyasını tarayıcıya doğrudan bağlamak yerine mevcut araç zincirinin ürettiği JavaScript'i kullan. Expo'ya geçerken onun proje ayarlarından başla; aşağıdaki küçük deneme ayarlarını bütün projeye körlemesine kopyalama.57
Birinci basamak: dosya JavaScript kalabilir
// @ts-check
/**
* @param {boolean} dogruMu
* @param {number} seri
* @returns {number}
*/
export function puanHesapla(dogruMu, seri) {
if (!dogruMu) return 0;
const bonus = seri >= 7 ? 5 : 0;
return 10 + bonus;
}
İlk yorum editörün TypeScript desteğine dosyayı denetlemesini söyler; @param satırları parametreleri, @returns dönüşü anlatan JSDoc tip belirtimleridir (JSDoc type annotations). Kalan satırlar normal JavaScript'tir: fonksiyon dışa aktarılır, yanlış cevapta erken döner, seri koşulundan bonus hesaplar ve toplar. Yorumlar tarayıcının yeni bir sözdizimi öğrenmesini gerektirmez. Komut satırında örneğin npx tsc --allowJs --checkJs --noEmit puan.js ile denetlenebilir.98
İkinci basamak: aynı davranışı tiplerle yazmak
// puan.ts
export function puanHesapla(dogruMu: boolean, seri: number): number {
if (!dogruMu) return 0;
const bonus = seri >= 7 ? 5 : 0;
return 10 + bonus;
}
Burada JSDoc'taki sözleşme doğrudan fonksiyon imzasına taşındı. 27.1'deki örnek kural aynı: doğru cevap ve seri 7 için 10 + 5 = 15, doğru cevap ve seri 6 için 10, yanlış cevap için 0. Bu üç durum Python hesabıyla ve örneklerin gerçek çalıştırılmasıyla karşılaştırıldı. Bunlar uygulamanın değişmez ürün kararı değil, kitapta kullanılan örnek kuraldır. Tip eklemek tek başına bu kuralı değiştirmedi.5
Bir proje ayarı neyi değiştirir?
TypeScript yapılandırma dosyası (tsconfig.json), denetlenecek dosyaları ve derleyici seçeneklerini bir yerde toplar. Şu, yalnızca küçük bir src klasörü için denetim örneğidir; JavaScript çıktısını başka araç üreteceği için burada çıktı yazmayı kapatıyoruz:78
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"strict": true,
"noEmit": true
},
"include": ["src/**/*"]
}
allowJs JavaScript dosyalarını da proje girdisi olarak kabul eder; checkJs o JavaScript dosyalarındaki hataların raporlanmasını açar; ikisi aynı iş değildir. strict sıkı tip denetimi grubunu açar; noEmit JavaScript çıktı yazılmasını engeller. include dosyaların hangi klasörden alınacağını söyler. npx tsc --project tsconfig.json bu proje ayarını kullanır. noEmit olan bu denetim örneğinden çalıştırılacak dosya bekleme; 30.3'teki bağımsız derleme deneyinde çıktı üretimini ayrıca yaptık.8
HTML ve CSS dosyaları TypeScript dosyası yapılmaz. Düz davranış modülü .ts olabilir; bir sonraki bölümde göreceğin JSX sözdizimini içeren TypeScript dosyasının uzantısı .tsx'tir. Uzantıyı değiştirmek tek başına bir dönüştürücü, paketleyici ya da doğru proje ayarı kurmaz. Resmî geçiş rehberinin de uyardığı gibi dönüşümden sonra görülen tip hatalarını tek tek incelemek gerekir; bazıları gerçek hata, bazıları da kodun niyetini daha açık anlatma ihtiyacıdır.7
Özellikle form girdilerinde bu fark somuttur. Bir input öğesinin value özelliği karakter dizisidir; kutuda “70” görünmesi onun sayı 70 olduğu anlamına gelmez. TypeScript bunu as number yazarak gerçek sayıya dönüştürmez. Dönüştürme ve girdinin kabul edilebilir olup olmadığını kontrol etme, gerçek JavaScript davranışlarıyla yapılır. 26.2'deki olay kodunu taşırken böyle sınırları görürsen denetleyiciyi susturmadan önce girdinin gerçekte ne olduğuna bak.113
Bölüm 21: statik ve dinamik ayrımını somutlaştırdık: TypeScript kaynak üzerinde çalışmadan önce, JavaScript gerçek değerlerle çalışırken iş görür. Bölüm 27: küçük modülün arayüzü artık tiplerle de anlatılır; puan hesabının gerçekleme ayrıntısı içeride kalır. Bölüm 29: paketleyici, dönüştürücü, denetleyici ve biçimlendirici farklı işler yapıyordu; tip denetimi de “kodu biçimlendir” ya da “ekran açıldı” haberine indirgenmez. Hepsini tek build betiğine bağlamak mümkün, ama hangi denetimlerin gerçekten çalıştığını bilmelisin.58
Bu bölümde neler kuruldu
TypeScript, JavaScript'e çalışmadan önce denetlenen sözleşmeler ekler. Tip belirtimi tipi açıkça söyler; tip çıkarımı bunu her yerde tekrar yazma ihtiyacını azaltır. interface nesne şekline, type bir tipe ad verir; birleşim tipi olası değerleri bir araya getirir, tip daraltma gerçek kontrollerden hangi olasılığın kaldığını çıkarır. tsc tip denetler ve istenirse JavaScript üretir; tip belirtimleri bu sırada silinir. as doğrulama değildir, any denetimi zayıflatır, unknown kullanmadan önce daraltmayı gerektirir. Genel tipler farklı tipler için giriş–çıkış ilişkisini korur. Geçiş tek hamlede olmak zorunda değildir: JavaScript + JSDoc, küçük bir TypeScript modülü, sonra ortak veri sözleşmeleri.
Terimler sözlükte, Bölüm 30 başlığı altında. Sıradaki Bölüm 31'de bu sözleşmeleri kullanıcı arayüzünün parçalarına vereceğiz; TypeScript'i tekrar baştan anlatmadan bileşenlerin beklediği girdileri okuyacağız.
Önce TypeScript Handbook, TypeScript for JavaScript Programmers: özellikle Types by Inference, Defining Types, Composing Types ve Structural Type System. Derinleşmek için The Basics, “Static type-checking”, “Emitting with Errors” ve “Erased Types”; ardından Narrowing, “typeof type guards” ve “Equality narrowing”.
Takıldığın yeri yapay zekâ öğretmenine sor. Ondan number | null alan küçük bir fonksiyon vermesini, önce senin hangi dallarda hangi tipin kaldığını yazmanı iste. Sonra bir as ekleyip “bu satır gerçek veride neyi kontrol etti?” sorusunu tartışın. Cevabı “hiçbir şeyi” ise bölümün en önemli ayrımını yakaladın.
Bölüm sonu soruları
Kitaba bakmadan cevapla. Tip denetimi ile çalışma sırasını özellikle ayır.
let puan = 70 satırında açık tip belirtimi yok. Ne olur?number bilgisini bulur. Açık belirtimin yazılmaması denetimin kapandığı anlamına gelmez. Bkz. 30.2.number | null alan bir parametredeki | ne söyler?null ihtimali elenmelidir. Bkz. 30.2.tsc --noEmit komutundaki --noEmit neyi kapatır?as PuanKaydi ile JSON sonucuna tip iddiası eklemek ne yapar?as çalışma sırasında denetim eklemez ve dönüşüm yapmaz; tip iddiası derlemede silinir. Gerçek veri doğrulaması JavaScript koşullarıyla yapılır. Bkz. 30.3.ilk<T>(liste: T[]): T | undefined neyi korur?T eleman tipini girişten çıkışa taşır; undefined boş dizi ihtimalini korur. Dizi sırasının veya boyunun değişmezliği vaat edilmez. Bkz. 30.4.allowJs ile checkJs arasındaki ayrım nedir?allowJs JavaScript dosyalarını girdiye katar; checkJs o dosyalarda hata raporlamayı açar. Dosyayı kabul etmek ile denetlemek ayrı seçeneklerdir. Bkz. 30.5.puan: number sözleşmesinden hangisi çıkarılabilir?number sayı bekler; pozitiflik ve artma gibi ürün kurallarını tek başına ifade etmez. Tiplerin güvencesini iş kuralının doğruluğuyla karıştırma. Bkz. 30.1.as PuanKaydi ekledim, artık güvenli” diyor. Kendi cümlelerinle neden yeterli olmadığını anlat. Tip denetimi, tiplerin silinmesi, JSON ayrıştırma ve gerçek doğrulamanın yerlerini ayır.Örnek cevabı göster
Tip iddiası derleyiciye bir varsayım verir; sunucudan gelecek değeri o anda incelemez. Derleme sırasında silinir. JSON ayrıştırma metnin geçerli JSON olmasını denetler; puanın sayı olması ayrı bir kuraldır. Gelen değeri unknown kabul edip nesne mi, puan özelliği var mı, sayı mı diye çalışma sırasında kontrol etmek gerekir; ürünün pozitiflik gibi ek kuralları varsa onlar da ayrıca yazılır. Bkz. 30.3.
Örnek cevabı göster
number | null kullanırım; seçim yoksa null, ilk şık için 0. if (secili) yerine secili === null diye kontrol ederim. Geçişte önce küçük puan dosyasına // @ts-check ve JSDoc parametre tipleri ekleyebilir, sonra aynı davranışı .ts dosyasında açık tiplerle ifade edebilirim. Her uzantıyı aynı anda değiştirmek gerekmez; denetleme ile çalıştırma adımlarını ayrı doğrularım. Bkz. 30.2 ve 30.5.
Kaynaklar
- TypeScript Handbook, The TypeScript Handbook, “About this Handbook”: JavaScript programlarına statik tip denetimi eklemenin amacı.
- TypeScript Handbook, TypeScript for JavaScript Programmers: çıkarım, nesne şekilleri, birleşimler, genel tipler ve yapısal tipleme.
- TypeScript Handbook, Everyday Types: temel tipler, diziler, fonksiyonlar, isteğe bağlı özellikler, birleşimler, tip takma adları, arayüzler, tip iddiaları, sabit değer tipleri ve yokluk denetimi.
- TypeScript Handbook, Narrowing:
typeof, eşitlik,in, kontrol akışı, sıfır venulltuzakları. - TypeScript Handbook, The Basics, “Static type-checking”, “Types for Tooling”, “tsc, the TypeScript compiler”, “Emitting with Errors”, “Erased Types” ve “Strictness”.
- TypeScript Handbook, Generics, “Hello World of Generics”, “Working with Generic Type Variables” ve “Generic Types”: tip bilgisini giriş ile çıkış arasında koruma.
- TypeScript Handbook, Migrating from JavaScript: kaynak–çıktı ayrımı,
allowJs, tek dosyayla başlama,.ts/.tsxve sıkılık ayarları. - TypeScript, TSConfig Reference:
allowJs,checkJs,strict,noEmit,noEmitOnErrorveincludeseçenekleri. - TypeScript Handbook, Type Checking JavaScript Files: JavaScript dosyalarının denetlenmesi ve JSDoc ile tip anlatımı.
- MDN, JSON.parse(), “Return value” ve “Exceptions”: JSON metninden değer üretme ile sözdizimi hatası; uygulamaya özgü şekil denetiminden ayrılır.
- MDN, HTMLInputElement: value property: girdi değerinin karakter dizisi olması.