Mert Özen Her Satırda Daha İleri
Frontend TypeScript Orta — #05

TypeScript'te keyof ve typeof: Değerlerden ve Key'lerden Tip Türetmek

Mert Özen 3 Kas 2026 8 dk 1 görüntülenme
TypeScript'te keyof ve typeof: Değerlerden ve Key'lerden Tip Türetmek

Tip seviyesindeki typeof ile değerlerden tip çıkarmayı, keyof ile bir tipin key'lerinden union üretmeyi, index signature'daki sürprizi ve keyof typeof kalıbını örneklerle görüyoruz.

Sürüm notu: Bu yazıdaki kodlar TypeScript 7.0 ve Node 26 içindir.

Önce Tahmin Et

Bu kez hiçbir tipi elle yazmıyoruz; elimizdeki tek şey sıradan bir object:

const config = { host: "localhost", port: 8080, secure: false };

type Config = typeof config;
type ConfigKey = keyof Config;

const key: ConfigKey = "hostname";

İki soru: ConfigKey hangi type'a karşılık geliyor? Son satır derlenir mi? Cevap, keyof'un ne ürettiğini gördüğümüz bölümde.

İki Ayrı typeof

JavaScript serilerinden typeof operatörünü tanıyorsun: çalışma zamanında bir değerin türünü bir string olarak döndürür. TypeScript aynı kelimeyi ikinci bir iş için de kullanır. Fark, kelimenin nerede yazıldığında:

const port = 8080;
let retries = 3;

console.log(typeof port); // number

type Port = typeof port; // 8080
type Retries = typeof retries; // number

console.log içindeki typeof bir değer ifadesinin parçası. Kod çalışınca değerlendirilir ve "number" string'ini üretir. type Port = … satırındaki typeof ise bir tip ifadesinin içinde. Çalışma zamanında hiç var olmaz; derleyiciye "bu değişkenin tipini al" der. port bir const olduğu için tipi daha önce gördüğümüz literal type 8080, retries ise let olduğu için number.

Bunu iki ayrı dünya gibi düşünebilirsin: değerlerin dünyası ve tiplerin dünyası. const, let ve fonksiyonlar değer dünyasında; type ve interface tip dünyasında yaşar. Tip seviyesindeki typeof, değer dünyasından tip dünyasına açılan bir kapıdır.

typeof ile Değerden Tip Çıkarmak

Bu kapının asıl işe yaradığı yer, tipi zaten bir değerde yazılı olan şeyler. Aynı yapıyı bir kez object olarak, bir kez de interface olarak yazmak yerine değeri tek kaynak yaparsın:

const defaultSettings = {
  theme: "dark",
  fontSize: 14,
  notifications: { email: true, push: false },
};

type Settings = typeof defaultSettings;

function applySettings(settings: Settings): void {
  console.log(settings.theme, settings.notifications.push);
}

applySettings({ theme: "light", fontSize: 16, notifications: { email: false, push: true } });

Settings, iç içe object'ler dahil defaultSettings'in tüm yapısını taşıyor: { theme: string; fontSize: number; notifications: { email: boolean; push: boolean; }; }. Yarın defaultSettings'e yeni bir property eklersen Settings da kendiliğinden güncellenir.

typeof fonksiyonlarla da çalışır ve fonksiyonun tamamının tipini verir:

function createUser(name: string, age: number) {
  return { id: Date.now(), name, age };
}

type CreateUser = typeof createUser;
// (name: string, age: number) => { id: number; name: string; age: number; }

const createGuest: CreateUser = (name, age) => ({ id: 0, name, age });

Bir de as const ile birleşimine bakalım. Daha önce gördüğümüz gibi as const, değerleri literal ve readonly olarak çıkarır; typeof da bu dar tipi olduğu gibi yakalar:

const theme = { mode: "dark", accent: "teal" } as const;

type Theme = typeof theme;
// { readonly mode: "dark"; readonly accent: "teal"; }

keyof: Bir Tipin Key'lerinden Union

keyof'u generic constraint'leri konuşurken kısaca görmüştük. Şimdi ona yakından bakalım. keyof bir object tipi alır ve o tipin key'lerinden oluşan bir union üretir:

interface User {
  id: string;
  name: string;
  email: string;
}

type UserKey = keyof User; // "id" | "name" | "email"

const sortBy: UserKey = "name";
// Hata: Type '"phone"' is not assignable to type 'keyof User'.
const filterBy: UserKey = "phone";

Editörde UserKey'in üzerine geldiğinde çoğu zaman açılmış bir union değil, keyof User yazısını görürsün. Hata mesajı da aynı şekilde 'keyof User' diyor. Bu bir eksiklik değil; TypeScript tipi tanımladığın biçimde göstermeyi tercih eder. Anlamı yine "id" | "name" | "email"'dir.

Baştaki soruya şimdi cevap verebiliriz. typeof config, object'in tipini { host: string; port: number; secure: boolean; } olarak yakalar. keyof bundan key'leri çeker ve ConfigKey "host" | "port" | "secure" olur. Son satır bu yüzden derlenmez:

error TS2322: Type '"hostname"' is not assignable to type '"host" | "port" | "secure"'.

Asıl güzel olan şu: bu union'ı hiçbir yerde elle yazmadık. config'e yeni bir key eklendiğinde ConfigKey da onu kendiliğinden içerir. Key listesini ikinci bir yerde tutup güncellemeyi unutma riski ortadan kalkar.

keyof ve Index Signature

keyof'un sonucu her zaman bir literal union değildir. Daha önce gördüğümüz index signature'lı bir tipte durum farklıdır:

type Scores = { [player: string]: number };

type ScoreKey = keyof Scores; // string | number

const a: ScoreKey = "Ada";
const b: ScoreKey = 7;
// Hata: Type 'boolean' is not assignable to type 'string | number'.
const c: ScoreKey = true;

Neden string değil de string | number? Çünkü JavaScript'te object key'leri her zaman string'e çevrilir; scores[7] yazmak scores["7"] ile aynı property'yi okur. TypeScript bu davranışı tipine yansıtır: string key'li bir index signature, number key'leri de kabul eder. Array'ler için de benzer bir sürpriz var: keyof string[] sadece number değildir, "length", "push", "map" gibi tüm method ve property adlarını da içerir.

keyof typeof: İkisi Birlikte

En sık kullanacağın kalıp, ikisini yan yana yazmak. Bir değerden key union'ı çıkarmak istiyorsan önce typeof ile tipini alırsın, sonra keyof ile key'lerini:

const statusLabels = {
  draft: "Draft",
  published: "Published",
  archived: "Archived",
} as const;

type Status = keyof typeof statusLabels; // "draft" | "published" | "archived"

function getLabel(status: Status): string {
  return statusLabels[status];
}

console.log(getLabel("published")); // Published
// Hata: Argument of type '"deleted"' is not assignable to parameter of type '"archived" | "draft" | "published"'.
getLabel("deleted");

keyof typeof statusLabels sağdan sola okunur: önce typeof statusLabels object'in tipini verir, sonra keyof o tipin key'lerini. Sonuçta hem çalışma zamanında kullanılan bir object'imiz hem de ondan türeyen bir union type'ımız var ve ikisi asla birbirinden kopmaz. Yeni bir durum eklemek için object'e bir satır eklemen yeterli. Bu kalıp, ileride TypeScript'e özgü enum'lara erasable bir alternatif olarak tekrar karşımıza çıkacak.

keyof ile Generic Kısıtlar

keyof'un gücü, generic'lerle birleşince ortaya çıkar. Bir object array'inden belirli bir key'in değerlerini toplayan bir fonksiyon yazalım:

function pluck<T, K extends keyof T>(items: T[], key: K) {
  return items.map((item) => item[key]);
}

const people = [
  { name: "Ada", age: 36 },
  { name: "Grace", age: 45 },
];

const names = pluck(people, "name"); // string[]
const ages = pluck(people, "age"); // number[]
console.log(names, ages); // [ 'Ada', 'Grace' ] [ 36, 45 ]

K extends keyof T, ikinci argümanın T'nin key'lerinden biri olmasını şart koşuyor; pluck(people, "email") yazarsan daha yazarken hata alırsın. Dönüş tipini yazmadık, TypeScript onu çıkardı ve "name" için string[], "age" için number[] buldu. Peki bu dönüş tipini kendimiz yazmak istesek nasıl yazardık? "T'nin K key'indeki property'nin tipi" demenin bir yolu var ve o, bir sonraki yazının konusu.

Sık Düşülen Tuzaklar

Birinci tuzak, Object.keys'in keyof döndüreceğini sanmak:

const config = { host: "localhost", port: 8080, secure: false };

for (const key of Object.keys(config)) {
  // Hata: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ host: string; port: number; secure: boolean; }'.
  console.log(config[key]);
}

Object.keys her zaman string[] döndürür, ("host" | "port" | "secure")[] değil. Bu bir ihmal değil, bilinçli bir karar. Structural typing yüzünden bir object, tipinde yazandan fazla property taşıyabilir; çalışma zamanında Object.keys'in döndüreceği key'lerin sadece tipte yazanlar olacağının garantisi yoktur. Key'lerin tam listesinden eminsen Object.keys(config) as (keyof typeof config)[] diye bir type assertion yazabilirsin, ama bunun bir söz verme olduğunu bil.

İkinci tuzak, iki dünyayı karıştırmak. Tip beklenen yere bir değer yazarsan 'config' refers to a value, but is being used as a type here. Did you mean 'typeof config'? hatasını alırsın; çözümü mesajın kendisi söylüyor. Tersi de geçerli: console.log(User) gibi bir interface'i değer olarak kullanmaya çalışırsan 'User' only refers to a type, but is being used as a value here. hatası gelir, çünkü interface'ler çalışma zamanında yoktur.

Üçüncü tuzak, typeof'u bir fonksiyon çağrısının sonucuna uygulamaya çalışmak. type NewUser = typeof createUser("Ada", 36); yazmak bir syntax hatasıdır (';' expected.); tip seviyesindeki typeof sadece değişken ve property adlarıyla çalışır. Bir fonksiyonun dönüş tipini almanın doğru yolunu utility type'lar yazısında göreceğiz.

Küçük Bir Deneme

★☆☆ let mode = "dark"; ve const fixedMode = "dark"; yaz. typeof mode ve typeof fixedMode type'larını tahmin et, sonra editörde kontrol et.

★★☆ statusLabels örneğindeki as const'u sil. Status type'ı değişir mi? Peki typeof statusLabels değişir mi? İkisini editörde üzerlerine gelerek karşılaştır ve as const'un key'leri mi, value'ları mı etkilediğini söyle.

★★★ Derlenir mi, tahmin et: const key: keyof string[] = "length";, const index: keyof string[] = 0; ve const big: keyof string[] = 5n;. Hangisinin hata verdiğini bulduktan sonra keyof'un array'lerde neden bu kadar geniş bir union ürettiğini açıkla.

Bu yazıda bir tipin key'lerini keyof ile almayı öğrendik. Bir sonraki adım, o key'lerin arkasındaki tipleri almak. User["email"] ile tek bir property'nin tipine ulaşmayı, T[number] ile bir array'in element tipini çıkarmayı ve pluck'ın dönüş tipini kendimiz yazmayı indexed access types ile göreceğiz.