TypeScript'ın kesin tipleriyle katı nesne şekli eşleştirmeyi keşfedin. Beklenmedik özellikleri önleyip kod sağlamlığını artırın. Pratik uygulama ve en iyi yöntemleri öğrenin.
TypeScript Kesin Tipler: Sağlam Kod için Katı Nesne Şekli Eşleştirme
JavaScript'in bir üst kümesi olan TypeScript, web geliştirmenin dinamik dünyasına statik tipleme getirir. TypeScript, tip güvenliği ve kodun sürdürülebilirliği açısından önemli avantajlar sunsa da, yapısal tipleme sistemi bazen beklenmedik davranışlara yol açabilir. İşte "kesin tipler" kavramı burada devreye girer. TypeScript'in açıkça "kesin tipler" adında yerleşik bir özelliği olmasa da, TypeScript özelliklerinin ve tekniklerinin bir kombinasyonu aracılığıyla benzer davranışları elde edebiliriz. Bu blog yazısı, kod sağlamlığını artırmak ve yaygın hataları önlemek için TypeScript'te daha katı nesne şekli eşleştirmeyi nasıl uygulayacağımızı derinlemesine inceleyecektir.
TypeScript'ın Yapısal Tiplemesini Anlamak
TypeScript, tip uyumluluğunun, tiplerin bildirilmiş adlarından ziyade üyeleri tarafından belirlendiği yapısal tiplemeyi (ördek tiplemesi olarak da bilinir) kullanır. Bir nesne, bir tipin gerektirdiği tüm özelliklere sahipse, ek özelliklere sahip olup olmadığına bakılmaksızın o tiple uyumlu kabul edilir.
Örneğin:
interface Point {
x: number;
y: number;
}
const myPoint = { x: 10, y: 20, z: 30 };
function printPoint(point: Point) {
console.log(`X: ${point.x}, Y: ${point.y}`);
}
printPoint(myPoint); // myPoint 'z' özelliğine sahip olmasına rağmen bu sorunsuz çalışır
Bu senaryoda, TypeScript `myPoint`in `printPoint`e geçirilmesine izin verir çünkü gerekli `x` ve `y` özelliklerini içerir, fazladan bir `z` özelliğine sahip olsa bile. Bu esneklik kullanışlı olsa da, beklenmedik özelliklere sahip nesneleri yanlışlıkla geçirirseniz ince hatalara da yol açabilir.
Fazla Özelliklerle İlgili Sorun
Yapısal tiplemenin esnekliği bazen hataları gizleyebilir. Bir yapılandırma nesnesi bekleyen bir fonksiyonu ele alalım:
interface Config {
apiUrl: string;
timeout: number;
}
function setup(config: Config) {
console.log(`API URL: ${config.apiUrl}`);
console.log(`Timeout: ${config.timeout}`);
}
const myConfig = { apiUrl: "https://api.example.com", timeout: 5000, typo: true };
setup(myConfig); // TypeScript burada şikayet etmiyor!
console.log(myConfig.typo); // true yazdırır. Fazladan özellik sessizce var olur
Bu örnekte, `myConfig` fazladan bir `typo` özelliğine sahiptir. TypeScript bir hata vermez çünkü `myConfig` hala `Config` arayüzünü karşılamaktadır. Ancak, yazım hatası asla yakalanmaz ve eğer yazım hatası `typoo` olarak tasarlanmış olsaydı uygulama beklendiği gibi davranmayabilirdi. Bu önemsiz gibi görünen sorunlar, karmaşık uygulamalarda hata ayıklarken büyük baş ağrılarına dönüşebilir. Eksik veya yanlış yazılmış bir özelliği, nesnelerin içinde iç içe geçmiş nesnelerle uğraşırken tespit etmek özellikle zor olabilir.
TypeScript'te Kesin Tipleri Uygulama Yaklaşımları
Gerçek "kesin tipler" TypeScript'te doğrudan mevcut olmasa da, benzer sonuçlar elde etmek ve daha katı nesne şekli eşleştirmesi uygulamak için çeşitli teknikler şunlardır:
1. `Omit` ile Tip Onaylamaları Kullanma
The `Omit` yardımcı tipi, mevcut bir tipten belirli özellikleri hariç tutarak yeni bir tip oluşturmanıza olanak tanır. Bir tip onayı ile birleştirildiğinde, bu fazla özellikleri önlemeye yardımcı olabilir.
interface Point {
x: number;
y: number;
}
const myPoint = { x: 10, y: 20, z: 30 };
// Yalnızca Point'in özelliklerini içeren bir tip oluşturun
const exactPoint: Point = myPoint as Omit & Point;
// Hata: Type '{ x: number; y: number; z: number; }' is not assignable to type 'Point'.
// Object literal may only specify known properties, and 'z' does not exist in type 'Point'.
function printPoint(point: Point) {
console.log(`X: ${point.x}, Y: ${point.y}`);
}
//Düzeltme
const myPointCorrect = { x: 10, y: 20 };
const exactPointCorrect: Point = myPointCorrect as Omit & Point;
printPoint(exactPointCorrect);
Bu yaklaşım, eğer `myPoint` `Point` arayüzünde tanımlanmamış özelliklere sahipse bir hata verir.
Açıklama: `Omit
2. Nesneler Oluşturmak için Bir Fonksiyon Kullanma
Arayüzde tanımlanan özelliklere sahip nesneleri kabul eden bir fabrika fonksiyonu oluşturabilirsiniz. Bu yaklaşım, nesne oluşturma noktasında güçlü tip kontrolü sağlar.
interface Config {
apiUrl: string;
timeout: number;
}
function createConfig(config: Config): Config {
return {
apiUrl: config.apiUrl,
timeout: config.timeout,
};
}
const myConfig = createConfig({ apiUrl: "https://api.example.com", timeout: 5000 });
//Bu derlenmeyecektir:
//const myConfigError = createConfig({ apiUrl: "https://api.example.com", timeout: 5000, typo: true });
//Argument of type '{ apiUrl: string; timeout: number; typo: true; }' is not assignable to parameter of type 'Config'.
// Object literal may only specify known properties, and 'typo' does not exist in type 'Config'.
By returning an object constructed with only the properties defined in the `Config` interface, you ensure that no extra properties can sneak in. This makes it safer to create the config.
3. Tip Korumalarını Kullanma
Tip korumaları, belirli bir kapsamdaki bir değişkenin tipini daraltan fonksiyonlardır. Doğrudan fazla özellikleri engellemeseler de, bunları açıkça kontrol etmenize ve uygun eylemi yapmanıza yardımcı olabilirler.
interface User {
id: number;
name: string;
}
function isUser(obj: any): obj is User {
return (
typeof obj === 'object' &&
obj !== null &&
'id' in obj && typeof obj.id === 'number' &&
'name' in obj && typeof obj.name === 'string' &&
Object.keys(obj).length === 2 //anahtar sayısını kontrol eder. Not: kırılgan ve User'ın tam anahtar sayısına bağlıdır.
);
}
const potentialUser1 = { id: 123, name: "Alice" };
const potentialUser2 = { id: 456, name: "Bob", extra: true };
if (isUser(potentialUser1)) {
console.log("Geçerli Kullanıcı:", potentialUser1.name);
} else {
console.log("Geçersiz Kullanıcı");
}
if (isUser(potentialUser2)) {
console.log("Geçerli Kullanıcı:", potentialUser2.name); //Buraya ulaşmayacaktır
} else {
console.log("Geçersiz Kullanıcı");
}
Bu örnekte, `isUser` tip koruması sadece gerekli özelliklerin varlığını değil, aynı zamanda tiplerini ve *tam* özellik sayısını da kontrol eder. Bu yaklaşım daha açık olup geçersiz nesneleri düzgün bir şekilde ele almanızı sağlar. Ancak, özellik sayısı kontrolü kırılgandır. Ne zaman `User` özellikleri kazanır/kaybederse, kontrolün güncellenmesi gerekir.
4. `Readonly` ve `as const` Kullanımı
While `Readonly` prevents modification of existing properties, and `as const` creates a read-only tuple or object where all properties are deeply read-only and have literal types, they can be used to create a stricter definition and type checking when combined with other methods. Although, neither prevents excess properties on its own.
interface Options {
width: number;
height: number;
}
//Salt okunur tipi oluşturun
type ReadonlyOptions = Readonly;
const options: ReadonlyOptions = { width: 100, height: 200 };
//options.width = 300; //hata: 'width' salt okunur bir özellik olduğu için atama yapılamaz.
//as const kullanarak
const config = { api_url: "https://example.com", timeout: 3000 } as const;
//config.timeout = 5000; //hata: 'timeout' salt okunur bir özellik olduğu için atama yapılamaz.
//Ancak, fazla özelliklere hala izin verilir:
const invalidOptions: ReadonlyOptions = { width: 100, height: 200, depth: 300 }; //hata yok. Hala fazla özelliklere izin verir.
interface StrictOptions {
readonly width: number;
readonly height: number;
}
//Bu şimdi hata verecektir:
//const invalidStrictOptions: StrictOptions = { width: 100, height: 200, depth: 300 };
//Type '{ width: number; height: number; depth: number; }' is not assignable to type 'StrictOptions'.
// Object literal may only specify known properties, and 'depth' does not exist in type 'StrictOptions'.
This improves immutability, but only prevents mutation, not the existence of extra properties. Combined with `Omit`, or the function approach, it becomes more effective.
5. Kütüphaneleri Kullanma (örn. Zod, io-ts)
Zod ve io-ts gibi kütüphaneler, güçlü çalışma zamanı tip doğrulama ve şema tanımlama yetenekleri sunar. Bu kütüphaneler, fazla özellikleri engellemek de dahil olmak üzere, verilerinizin beklenen şeklini kesin olarak tanımlayan şemalar tanımlamanıza olanak tanır. Bir çalışma zamanı bağımlılığı ekleseler de, çok sağlam ve esnek bir çözüm sunarlar.
Zod ile Örnek:
import { z } from 'zod';
const UserSchema = z.object({
id: z.number(),
name: z.string(),
});
type User = z.infer;
const validUser = { id: 1, name: "John" };
const invalidUser = { id: 2, name: "Jane", extra: true };
const parsedValidUser = UserSchema.parse(validUser);
console.log("Ayrıştırılmış Geçerli Kullanıcı:", parsedValidUser);
try {
const parsedInvalidUser = UserSchema.parse(invalidUser);
console.log("Ayrıştırılmış Geçersiz Kullanıcı:", parsedInvalidUser); // Buraya ulaşılmayacaktır
} catch (error) {
console.error("Doğrulama Hatası:", error.errors);
}
Zod'un `parse` yöntemi, giriş şemaya uymuyorsa bir hata fırlatacak ve böylece fazla özellikleri etkili bir şekilde önleyecektir. Bu, çalışma zamanı doğrulaması sağlar ve ayrıca şemadan TypeScript tipleri oluşturarak tip tanımlarınız ile çalışma zamanı doğrulama mantığınız arasında tutarlılık sağlar.
Kesin Tipleri Uygulama için En İyi Uygulamalar
TypeScript'te daha katı nesne şekli eşleştirmesini uygularken göz önünde bulundurmanız gereken bazı en iyi uygulamalar şunlardır:
- Doğru tekniği seçin: En iyi yaklaşım, özel ihtiyaçlarınıza ve proje gereksinimlerinize bağlıdır. Basit durumlar için, `Omit` veya fabrika fonksiyonları ile tip onaylamaları yeterli olabilir. Daha karmaşık senaryolar veya çalışma zamanı doğrulaması gerektiğinde, Zod veya io-ts gibi kütüphaneleri kullanmayı düşünün.
- Tutarlı olun: Seçtiğiniz yaklaşımı kod tabanınızda tutarlı bir şekilde uygulayın, böylece tek tip bir tip güvenliği seviyesi sağlayın.
- Tiplerinizi belgeleyin: Verilerinizin beklenen şeklini diğer geliştiricilere iletmek için arayüzlerinizi ve tiplerinizi açıkça belgeleyin.
- Kodunuzu test edin: Tip kısıtlamalarınızın beklendiği gibi çalıştığını ve kodunuzun geçersiz verileri düzgün bir şekilde işlediğini doğrulamak için birim testleri yazın.
- Dezavantajları göz önünde bulundurun: Daha katı nesne şekli eşleştirmesi uygulamak kodunuzu daha sağlam hale getirebilir, ancak geliştirme süresini de artırabilir. Faydaları maliyetlere karşı tartın ve projeniz için en mantıklı yaklaşımı seçin.
- Aşamalı benimseme: Büyük, mevcut bir kod tabanında çalışıyorsanız, uygulamanızın en kritik bölümlerinden başlayarak bu teknikleri aşamalı olarak benimsemeyi düşünün.
- Nesne şekillerini tanımlarken tip takma adları yerine arayüzleri tercih edin: Arayüzler genellikle tercih edilir çünkü farklı dosyalar arasında tipleri genişletmek için yararlı olabilen bildirim birleştirmeyi desteklerler.
Gerçek Dünya Örnekleri
Kesin tiplerin faydalı olabileceği bazı gerçek dünya senaryolarına bakalım:
- API istek yükleri: Bir API'ye veri gönderirken, yükün beklenen şemaya uygun olduğundan emin olmak çok önemlidir. Kesin tipleri uygulamak, beklenmedik özelliklerin gönderilmesinden kaynaklanan hataları önleyebilir. Örneğin, birçok ödeme işleme API'si, beklenmedik verilere karşı son derece hassastır.
- Yapılandırma dosyaları: Yapılandırma dosyaları genellikle çok sayıda özellik içerir ve yazım hataları yaygın olabilir. Kesin tipleri kullanmak, bu yazım hatalarını erken yakalamaya yardımcı olabilir. Bir bulut dağıtımında sunucu konumları ayarlıyorsanız, bir konum ayarındaki (örn. eu-west-1 yerine eu-wet-1) bir yazım hatası, önceden yakalanmazsa hata ayıklaması son derece zor hale gelecektir.
- Veri dönüştürme işlem hatları: Verileri bir formattan diğerine dönüştürürken, çıktı verilerinin beklenen şemaya uygun olduğundan emin olmak önemlidir.
- Mesaj kuyrukları: Bir mesaj kuyruğu aracılığıyla mesaj gönderirken, mesaj yükünün geçerli olduğundan ve doğru özellikleri içerdiğinden emin olmak önemlidir.
Örnek: Uluslararasılaşma (i18n) Yapılandırması
Çok dilli bir uygulama için çevirileri yönettiğinizi düşünün. Şöyle bir yapılandırma nesneniz olabilir:
interface Translation {
greeting: string;
farewell: string;
}
interface I18nConfig {
locale: string;
translations: Translation;
}
const englishConfig: I18nConfig = {
locale: "en-US",
translations: {
greeting: "Hello",
farewell: "Goodbye"
}
};
//Bu bir sorun olacaktır, çünkü fazladan bir özellik mevcut, sessizce bir hata ortaya çıkarıyor.
const spanishConfig: I18nConfig = {
locale: "es-ES",
translations: {
greeting: "Hola",
farewell: "Adiós",
typo: "istem dışı çeviri"
}
};
//Çözüm: Omit Kullanma
const spanishConfigCorrect: I18nConfig = {
locale: "es-ES",
translations: {
greeting: "Hola",
farewell: "Adiós"
} as Omit & Translation
};
Kesin tipler olmadan, bir çeviri anahtarındaki (tıpkı bir `typo` alanı eklemek gibi) bir yazım hatası fark edilmeyebilir ve bu da kullanıcı arayüzünde eksik çevirilere yol açabilir. Daha katı nesne şekli eşleştirmesi uygulayarak, bu hataları geliştirme sırasında yakalayabilir ve üretime ulaşmalarını engelleyebilirsiniz.
Sonuç
TypeScript'in yerleşik "kesin tipler" özelliği olmasa da, `Omit` ile tip onaylamaları, fabrika fonksiyonları, tip korumaları, `Readonly`, `as const` ve Zod ve io-ts gibi harici kütüphaneler gibi TypeScript özelliklerinin ve tekniklerinin bir kombinasyonunu kullanarak benzer sonuçlar elde edebilirsiniz. Daha katı nesne şekli eşleştirmesi uygulayarak, kodunuzun sağlamlığını artırabilir, yaygın hataları önleyebilir ve uygulamalarınızı daha güvenilir hale getirebilirsiniz. İhtiyaçlarınıza en uygun yaklaşımı seçmeyi ve kod tabanınızda tutarlı bir şekilde uygulamayı unutmayın. Bu yaklaşımları dikkatlice değerlendirerek, uygulamanızın tipleri üzerinde daha fazla kontrol sahibi olabilir ve uzun vadeli sürdürülebilirliği artırabilirsiniz.