TypeScript sınıfları JavaScript'teki class yapısının üzerine tip denetimi ekler. Üyelerin kimler tarafından görülebileceğini public (her yerden), protected (sınıfın kendisi ve alt sınıfları) ve private (yalnızca sınıfın içi) ile belirlersiniz; readonly bir alanın constructor dışında değiştirilmesini engeller. Bu belirleyicilerin hepsi derleme zamanında denetlenir. Çalışma zamanında da gizli kalması gereken bir alan için JavaScript'in #alan sözdizimini kullanmanız gerekir.

Bu yazıda erişim belirleyicileri, TypeScript private ile JavaScript #private farkını, parameter properties kısayolunu, abstract sınıfları, implements ve override anahtar kelimelerini derleyicinin verdiği gerçek hata mesajlarıyla anlatıyoruz. Sınıfların JavaScript tarafını (constructor, kalıtım, getter/setter) bilmiyorsanız önce JavaScript class yazısına göz atın. Serinin tamamı için TypeScript öğrenme rehberi sayfasına bakabilirsiniz.

Erişim belirleyicileri: public, protected, private

Erişim belirleyicileri: public, protected, private: public (varsayılan), protected, private, #alan (JavaScript)

Bir üyeye belirleyici yazmazsanız o üye public olur; yani public yazmak zorunlu değildir, yalnızca okunabilirlik için tercih edilir. protected üyeye sınıfın kendisi ve ondan türeyen sınıflar erişebilir. private ise alt sınıflara bile kapalıdır. Aşağıdaki örnek --strict --target es2022 ile derlendi; hata veren satırların yanında tsc 6.0'ın mesajları var:

typescript
class Hesap {
  public sahip: string;
  protected bakiye: number;
  private pin: string;
  readonly iban: string;

  constructor(sahip: string, iban: string, pin: string) {
    this.sahip = sahip;
    this.iban = iban;   // readonly alana constructor içinde atama serbest
    this.pin = pin;
    this.bakiye = 0;
  }

  yatir(tutar: number) {
    this.bakiye += tutar;
  }
}

class VadeliHesap extends Hesap {
  faizEkle(oran: number) {
    this.bakiye += this.bakiye * oran; // protected: alt sınıfta erişilebilir
    console.log(this.pin);
    // TS2341: Property 'pin' is private and only accessible within class 'Hesap'.
  }
}

const h = new Hesap("Ayşe", "TR00 0000", "1234");
h.yatir(500);
console.log(h.bakiye);
// TS2445: Property 'bakiye' is protected and only accessible within class 'Hesap' and its subclasses.
console.log(h.pin);
// TS2341: Property 'pin' is private and only accessible within class 'Hesap'.
h.iban = "TR11 1111";
// TS2540: Cannot assign to 'iban' because it is a read-only property.
BelirleyiciSınıfın içiAlt sınıfSınıf dışıÇalışma zamanında gizli mi
public (varsayılan)EvetEvetEvetHayır
protectedEvetEvetHayırHayır
privateEvetHayırHayırHayır
#alan (JavaScript)EvetHayırHayırEvet

Handbook iki ayrıntıya dikkat çeker. Birincisi, alt sınıf bir protected üyeyi belirleyici yazmadan yeniden tanımlarsa o üye public hâle gelir; bunu istemiyorsanız alt sınıfta da protected yazmanız gerekir. İkincisi, TypeScript aynı sınıfın farklı örneklerinin birbirinin private üyesine erişmesine izin verir; örneğin esitMi(diger: Hesap) metodu içinde diger.pin okunabilir.

TypeScript private ile JavaScript #private farkı

TypeScript'in private belirleyicisi yalnızca tip denetimi sırasında uygulanır. Derlenen JavaScript'te alan sıradan bir özelliğe dönüşür. Handbook bu yüzden ona "soft private" der: köşeli parantezle erişime (k["anahtar"]) derleyici bile izin verir. JavaScript'in # ile başlayan özel alanları ise derlemeden sonra da özel kalır ("hard private").

typescript
class Kasa {
  private anahtar = "ts-gizli";
  #sifre = "js-gizli";

  sifreUzunlugu() {
    return this.#sifre.length;
  }
}

const k = new Kasa();
console.log(k["anahtar"]);       // ts-gizli  (derleyici izin verir)
console.log(Object.keys(k));     // [ 'anahtar' ]
console.log(k.sifreUzunlugu());  // 8

// console.log(k.#sifre);
// TS18013: Property '#sifre' is not accessible outside class 'Kasa' because it has a private identifier.

Çıktıdaki Object.keys(k) sonucuna bakın: anahtar görünüyor, #sifre görünmüyor. es2022 hedefiyle derlenen kodda #sifre olduğu gibi kalır. Handbook'a göre hedef ES2021 veya daha eskiyse TypeScript # alanlarını WeakMap kullanarak taklit eder.

readonly ne zaman işe yarar

readonly ne zaman işe yarar

readonly alanlara yalnızca tanımlandıkları yerde ya da constructor içinde değer atanabilir. Sınıfın başka bir metodunda veya dışarıda atama yapılırsa yukarıdaki TS2540 hatasını alırsınız. Bu da bir derleme zamanı kuralıdır; çalışma zamanında nesneyi dondurmaz. Değerin gerçekten değişmemesi gerekiyorsa Object.freeze() gibi JavaScript araçlarına bakın. Dizi ve tuple'larda readonly kullanımı için tuple ve readonly dizi yazısına göz atabilirsiniz.

Parameter properties: alanı constructor parametresinde tanımlamak

Bir constructor parametresinin önüne public, protected, private veya readonly yazarsanız TypeScript aynı adlı bir alan oluşturur ve parametre değerini ona atar. Buna parameter properties denir:

typescript
class Urun {
  constructor(
    public readonly kod: string,
    public fiyat: number,
    private stok: number,
  ) {}

  stoktaMi() {
    return this.stok > 0;
  }
}

const u = new Urun("KLV-01", 750, 3);
console.log(u.kod, u.fiyat, u.stoktaMi()); // KLV-01 750 true

tsc bu sınıfı es2022 hedefinde şu JavaScript'e çevirir. Gördüğünüz gibi parameter properties çalışma zamanında kod üreten bir TypeScript özelliğidir:

javascript
class Urun {
    kod;
    fiyat;
    stok;
    constructor(kod, fiyat, stok) {
        this.kod = kod;
        this.fiyat = fiyat;
        this.stok = stok;
    }
    stoktaMi() {
        return this.stok > 0;
    }
}

abstract sınıflar ve üyeler

abstract sınıf doğrudan new ile oluşturulamaz; alt sınıflar için ortak bir taban görevi görür. abstract olarak işaretlenen metot veya alanın gövdesi yoktur ve somut (abstract olmayan) her alt sınıf bu üyeyi uygulamak zorundadır:

typescript
abstract class Bildirim {
  constructor(protected alici: string) {}

  abstract gonder(mesaj: string): string;

  kaydet(mesaj: string) {
    return `[kayıt] ${this.gonder(mesaj)}`;
  }
}

class EpostaBildirim extends Bildirim {
  gonder(mesaj: string) {
    return `${this.alici} adresine e-posta: ${mesaj}`;
  }
}

const b = new EpostaBildirim("ali@ornek.com");
console.log(b.kaydet("Siparişiniz yolda"));
// [kayıt] ali@ornek.com adresine e-posta: Siparişiniz yolda

const x = new Bildirim("biri");
// TS2511: Cannot create an instance of an abstract class.

class SmsBildirim extends Bildirim {}
// TS2515: Non-abstract class 'SmsBildirim' does not implement inherited abstract member gonder from class 'Bildirim'.

Bir fonksiyonun "bu taban sınıftan türeyen herhangi bir somut sınıfı" alması gerekiyorsa parametre tipini typeof Bildirim yerine new (alici: string) => Bildirim gibi bir construct signature olarak yazın. Handbook'un açıkladığı gibi typeof Bildirim abstract sınıfın kendisini de kabul ettiğinden fonksiyon içinde new çağrısı TS2511 verir.

implements: sınıfı bir interface'e göre denetlemek

implements bir sınıfın belirli bir interface şeklini karşıladığını kontrol ettirir. Eksik üye varsa derleyici hangi üyenin eksik olduğunu söyler. Bir sınıf virgülle ayırarak birden fazla interface uygulayabilir. Interface ile type alias farkı için interface ve type yazısına bakın.

typescript
interface Depolama {
  oku(anahtar: string): string | undefined;
  yaz(anahtar: string, deger: string): void;
}

class BellekDepo implements Depolama {
  private veri = new Map<string, string>();
  oku(anahtar: string) {
    return this.veri.get(anahtar);
  }
  yaz(anahtar: string, deger: string) {
    this.veri.set(anahtar, deger);
  }
}

class EksikDepo implements Depolama {
  oku(anahtar: string) {
    return undefined;
  }
}
// TS2420: Class 'EksikDepo' incorrectly implements interface 'Depolama'.
//   Property 'yaz' is missing in type 'EksikDepo' but required in type 'Depolama'.

Sık karıştırılan nokta şu: implements yalnızca bir kontroldür, sınıfın tipini değiştirmez. Metot parametrelerinin tipi interface'ten otomatik alınmaz. Parametreye tip yazmazsanız strict modda örtük any hatası alırsınız:

typescript
interface Denetleyici {
  denetle(ad: string): boolean;
}

class AdDenetleyici implements Denetleyici {
  denetle(s) {
    // TS7006: Parameter 's' implicitly has an 'any' type.
    return s.length > 2;
  }
}

override ve noImplicitOverride

Alt sınıfta taban sınıftaki bir metodu yeniden yazarken override anahtar kelimesini kullanabilirsiniz. Taban sınıfta öyle bir üye yoksa derleyici hata verir; böylece taban sınıftaki bir metodun adı değiştiğinde alt sınıfta "sahipsiz" kalan metotları yakalarsınız. noImplicitOverride seçeneği açıksa override yazmak zorunlu hâle gelir. Bu seçenek strict ailesine dahil değildir, ayrıca açmanız gerekir.

typescript
class Taban {
  selamla() {
    return "Merhaba";
  }
}

class Turev extends Taban {
  override selamla() {
    return super.selamla() + ", dünya";
  }
  override vedalas() {
    // TS4113: This member cannot have an 'override' modifier because it is not declared in the base class 'Taban'.
    return "Hoşça kal";
  }
}

// --noImplicitOverride açıkken override yazılmazsa:
class Diger extends Taban {
  selamla() {
    // TS4114: This member must have an 'override' modifier because it overrides a member in the base class 'Taban'.
    return "Selam";
  }
}

Alt sınıfın taban sınıfın sözleşmesine uyması da gerekir: Handbook'taki örnekte taban sınıfta parametresiz olan greet() metodu alt sınıfta zorunlu bir parametreyle yeniden yazıldığında TS2416 hatası oluşur, çünkü alt sınıf örneği taban sınıf tipinde bir değişkene atandığında parametresiz çağrılabilir.

Sık yapılan hatalar

Sık yapılan hatalar: private alanın çalışma zamanında da gizli olduğunu sanmak. Değer derlenen..., implements yazınca metot parametrelerinin tipinin otomatik geleceğini...
  • private alanın çalışma zamanında da gizli olduğunu sanmak. Değer derlenen JavaScript'te sıradan bir özelliktir; gerçek gizlilik için #alan kullanın.
  • implements yazınca metot parametrelerinin tipinin otomatik geleceğini düşünmek. Tipleri sınıfta da yazmanız gerekir.
  • Alt sınıfta protected bir üyeyi belirleyicisiz yeniden tanımlayıp istemeden public yapmak.
  • Node.js'in tip silme moduyla çalışan bir projede parameter properties kullanmak. --erasableSyntaxOnly açıkken bu TS1294 hatası verir.
  • Constructor dışındaki bir metotta alanı ilk kez atamak. strictPropertyInitialization yalnızca constructor içindeki atamaları sayar; konunun ayrıntısı strict modu yazısında.

Sık Sorulan Sorular

private mi kullanmalıyım, #private mi?

Yalnızca kendi kodunuzun yanlışlıkla erişmesini engellemek istiyorsanız private yeterlidir ve testlerde köşeli parantezle erişime izin verir. Değerin çalışma zamanında da dışarıdan okunamaması gerekiyorsa JavaScript'in #alan sözdizimini seçin.

public yazmak zorunlu mu?

Hayır. Belirleyicisi olmayan her sınıf üyesi zaten public kabul edilir. Bazı ekipler okunabilirlik için yine de açıkça yazar.

abstract sınıf ile interface arasındaki fark nedir?

Interface yalnızca şekli tarif eder ve derlenen JavaScript'te iz bırakmaz. Abstract sınıf ise gerçek bir sınıftır: ortak metot gövdeleri ve alanlar içerebilir, alt sınıflar onu extends ile genişletir. Bir sınıf tek bir sınıftan türeyebilir ama birden fazla interface uygulayabilir.

readonly ile const arasındaki fark nedir?

const bir değişkenin yeniden atanmasını engeller; readonly ise bir nesne özelliğine veya sınıf alanına uygulanır ve atamayı constructor ile sınırlar. İkisi de alanın içindeki nesneyi dondurmaz.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: TypeScript öğrenme rehberi · JavaScript class · interface ve type farkı · TypeScript decorators · strict modu · TypeScript ileri seviye