TypeScript zengin bir tür sistemine sahiptir, ancak verimli olmak için tamamını anlamanız gerekmez. Gerçek şu ki, işin %90'ı her gün kullandığınız bir avuç özellik tarafından yapılır. Bu pratik referans — primitifler, union'lar, genericler, yardımcı türler ve her şeyin yerine oturmasını sağlayan narrowing desenleri. Soyut teori yok; her örnek gerçek bir projede yazdığınız türden kodlar. JavaScript'i iyi biliyorsanız ve biraz TypeScript yaptıysanız ama zihinsel modelinizi sağlamlaştırmak istiyorsanız, bu tam size göre. Tam hikaye TypeScript El Kitabı'ndadır — bu makale 80/20 versiyonudur.

Primitifler ve Literaller

TypeScript'in primitif türleri doğrudan JavaScript'in çalışma zamanı türleriyle eşleşir: string, number, boolean, null, undefined, bigint ve symbol. İlk üçünü sürekli kullanırsınız; geri kalanları belirli bağlamlarda ortaya çıkar.

ts
// Primitives — the foundation
let userId: number = 42;
let username: string = 'alice';
let isActive: boolean = true;
let deletedAt: Date | null = null;     // nullable pattern
let refreshToken: string | undefined;  // optional pattern

TypeScript'in ilginçleştiği yer literal türlerdir. Bir değerin yalnızca string olduğunu söylemek yerine, belirli bir string kümesinden biri olması gerektiğini söyleyebilirsiniz. Bu, düz bir string açıklamasından çok daha kullanışlıdır çünkü TypeScript geçersiz değerleri derleme zamanında yakalayabilir:

ts
type Direction = 'north' | 'south' | 'east' | 'west';
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
type StatusCode = 200 | 201 | 400 | 401 | 403 | 404 | 500;

function navigate(dir: Direction) {
  console.log(`Moving ${dir}`);
}

navigate('north');  // ✅
navigate('up');     // ❌ Argument of type '"up"' is not assignable to type 'Direction'

// Combine with string for "known values plus free-form"
type EventName = 'click' | 'focus' | 'blur' | (string & {});
Sondaki (string & {}) hilesi, herhangi bir string'i kabul ederken bilinen değerler için otomatik tamamlamayı çalışır durumda tutar. Design system kütüphanelerinde yaygın bir desendir.

interface ve type — Hangisini Ne Zaman Kullanmalı

Bu, her TypeScript yeni başlayanının sorduğu sorudur. Pratik cevap: nesne şekilleri için interface kullanın — özellikle genel API sözleşmelerini temsil edenler veya diğer türlerin genişleteceği olanlar için. Union'lar, kesişimler, eşlenmiş türler için type kullanın ve tamamen bir nesne şekli olmayan her şey için. Pratikte nesne şekilleri için büyük ölçüde birbirinin yerine kullanılabilirler — bir kural belirleyin ve kod tabanında tutarlı olun.

ts
// interface — object shapes, extensible via extends
interface User {
  id: number;
  email: string;
  createdAt: Date;
}

interface AdminUser extends User {
  role: 'admin';
  permissions: string[];
}

// type — unions, intersections, aliases for anything
type ID = string | number;
type Nullable<T> = T | null;

// Intersection — combine two shapes (common for mixins / HOC props)
type WithTimestamps = {
  createdAt: Date;
  updatedAt: Date;
};

type UserRecord = User & WithTimestamps;

// type is required here — interface can't express a union of shapes
type ApiResponse<T> =
  | { status: 'success'; data: T }
  | { status: 'error'; error: string; code: number };

Anlamlı bir fark: interface'ler declaration merging'i destekler — aynı interface'i iki kez bildirebilirsiniz ve TypeScript tanımları birleştirir. Kütüphanelerin global türleri genişletmesi bu şekilde olur (örn. Window'a özellikler eklemek). Type'lar birleşmez; bir türü yeniden bildirmek bir hatadır.

Union ve Kesişim Türleri

Union türleri (A | B) "bu değer A veya B'dir" der. Kesişim türleri (A & B) "bu değer aynı anda hem A hem de B'dir" der. Union'lar gerçek kodda her yerdedir — sağladıkları en güçlü desen ayrımcılı union'dır; bu, her yerde opsiyonel alanlara başvurmadan farklı durumlarda olabilen verileri modellemenin yoludur.

ts
// Discriminated union — model API response states cleanly
type FetchState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: string; retryable: boolean };

// TypeScript narrows the type inside each branch
function render<T>(state: FetchState<T>) {
  switch (state.status) {
    case 'idle':    return '<p>Not started</p>';
    case 'loading': return '<p>Loading...</p>';
    case 'success': return `<pre>${JSON.stringify(state.data)}</pre>`;
    case 'error':   return `<p>Error: ${state.error}</p>`;
  }
}

// Intersection — common in React/Angular for composing prop types
type ButtonBaseProps = {
  label: string;
  disabled?: boolean;
};

type IconButtonProps = ButtonBaseProps & {
  icon: string;
  iconPosition: 'left' | 'right';
};

Ayrımcılı union deseni bir ayrımcı'ya dayanır — her varyanta özgü bir literal özellik (burada status). TypeScript, tam türü koşullu dallar içinde daraltmak için bu özelliği kullanır ve size data veya error gibi varyanta özgü alanlara tür güvenli erişim sağlar.

Genericler — Herkesi Tökezleten Kısım

Motive edici sorun: birden fazla tür üzerinde çalışan bir fonksiyon istiyorsunuz, ancak any kullanmak tüm tür bilgisini atıyor. Genericler bunu çözer — bunlar, bir fonksiyonun veya interface'in tam tür güvenliğini koruyarak bir tür ailesi üzerinde çalışmasına izin veren açılı parantezler içinde yazılan tür parametreleridir.

ts
// Without generics — any loses all information
function first(arr: any[]): any {
  return arr[0];
}
const x = first([1, 2, 3]); // x is 'any' — TypeScript can't help you here

// With generics — T is inferred from the argument
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}
const n = first([1, 2, 3]);       // n: number | undefined ✅
const s = first(['a', 'b', 'c']); // s: string | undefined ✅

<T> sözdizimi bir tür parametresi bildirir. İstediğiniz her şeyi adlandırabilirsiniz — T yalnızca tek bir generic tür için kullanılan kuraldır. Fonksiyonu çağırdığınızda, TypeScript T'yi argümanlardan çıkarır, bu nedenle nadiren açıkça yazmanız gerekir. Generic interface'ler, API şekillerini modellemek için de aynı derecede kullanışlıdır:

ts
// Generic interface — the API wrapper pattern every codebase has
interface ApiResponse<T> {
  data: T;
  meta: {
    page: number;
    totalPages: number;
    totalItems: number;
  };
}

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

// The response type is fully typed — no casting needed
async function fetchUsers(): Promise<ApiResponse<User[]>> {
  const res = await fetch('/api/users');
  return res.json();
}

const response = await fetchUsers();
response.data[0].email; // ✅ TypeScript knows this is a string
response.meta.totalPages; // ✅ TypeScript knows this is a number

// Constrained generics — T must have an 'id' field
function findById<T extends { id: number }>(items: T[], id: number): T | undefined {
  return items.find(item => item.id === id);
}

const user = findById(users, 42); // T inferred as User
Genericlere ne zaman başvurulur: aynı fonksiyon imzasını farklı türlerle iki kez yazarken veya istediğiniz dönüş türünü elde etmek için as ile dönüştürürken. Bunlar bir generic'in daha temiz olacağının iki işaretidir.

Gerçekten Kullanacağınız Yardımcı Türler

TypeScript, mevcut türleri yeni türlere dönüştüren bir dizi yerleşik yardımcı tür içerir. Tür tanımlarını manuel olarak çoğaltma veya ince ayar yapma ihtiyacını ortadan kaldırırlar. İşte gerçek kod tabanlarında sürekli karşılaşılanlar:

ts
interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user';
  createdAt: Date;
}

// Partial<T> — all properties become optional
// Perfect for PATCH/update payloads
type UpdateUserPayload = Partial<User>;
// { id?: number; name?: string; email?: string; ... }

async function updateUser(id: number, payload: Partial<User>) {
  return fetch(`/api/users/${id}`, {
    method: 'PATCH',
    body: JSON.stringify(payload)
  });
}

// Required<T> — all properties become required (opposite of Partial)
interface Config {
  apiUrl?: string;
  timeout?: number;
  retries?: number;
}
type ResolvedConfig = Required<Config>;
// { apiUrl: string; timeout: number; retries: number }

// Pick<T, K> — keep only specified properties
type UserSummary = Pick<User, 'id' | 'name' | 'email'>;
// Use in list views where you only need the display fields

// Omit<T, K> — exclude specified properties
type PublicUser = Omit<User, 'role' | 'createdAt'>;
// Safe to expose in API responses

// Readonly<T> — prevents mutation (great for config and frozen state)
const config: Readonly<ResolvedConfig> = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retries: 3
};
// config.timeout = 10000; // ❌ Cannot assign to 'timeout' because it is a read-only property

// Record<K, V> — typed dictionary
type UserCache = Record<number, User>;
const cache: UserCache = {};
cache[42] = { id: 42, name: 'Alice', email: '[email protected]', role: 'user', createdAt: new Date() };

// Common pattern: mapping string keys to a known value shape
type FeatureFlags = Record<string, { enabled: boolean; rolloutPct: number }>;

// ReturnType<T> — infer the return type of a function
function createSession(userId: number) {
  return {
    token: crypto.randomUUID(),
    userId,
    expiresAt: new Date(Date.now() + 86_400_000)
  };
}

type Session = ReturnType<typeof createSession>;
// { token: string; userId: number; expiresAt: Date }
// — no need to define the type separately and keep it in sync

unknown vs any vs never

Bu üç tür, her birinin hangi sorunu çözdüğünü anlayana kadar çoğu geliştiriciyi şaşırtır. any kaçış kapısıdır — o değer için tür denetimini tamamen devre dışı bırakır. JavaScript'i TypeScript'e taşırken kullanışlıdır, ancak aşırı kullanım TypeScript'in amacını ortadan kaldırır. unknown tür güvenli alternatiftir: değer herhangi bir şey olabilir, ancak onunla bir şey yapabilmek için daraltmanız gerekir. never asla oluşamayan bir türdür — tür hiyerarşisinin en altı.

ts
// any — type checking disabled, use sparingly
function dangerousTransform(input: any) {
  return input.toUpperCase(); // TypeScript won't warn even if this crashes at runtime
}

// unknown — safe alternative, forces you to check before using
function safeTransform(input: unknown): string {
  if (typeof input === 'string') {
    return input.toUpperCase(); // ✅ narrowed to string inside this block
  }
  if (typeof input === 'number') {
    return input.toFixed(2);    // ✅ narrowed to number
  }
  throw new Error(`Cannot transform value of type ${typeof input}`);
}

// Common use: parsing untrusted data (API responses, localStorage, user input)
function parseConfig(raw: unknown): ResolvedConfig {
  if (
    typeof raw === 'object' &&
    raw !== null &&
    'apiUrl' in raw &&
    typeof (raw as any).apiUrl === 'string'
  ) {
    return raw as ResolvedConfig;
  }
  throw new Error('Invalid config format');
}

// never — the exhaustive switch pattern
type Shape = { kind: 'circle'; radius: number } | { kind: 'square'; side: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle': return Math.PI * shape.radius ** 2;
    case 'square': return shape.side ** 2;
    default:
      // If you add a new Shape variant and forget to handle it here,
      // TypeScript will error: Type 'NewShape' is not assignable to type 'never'
      const _exhaustive: never = shape;
      throw new Error(`Unhandled shape: ${_exhaustive}`);
  }
}

Pratikte Tür Daraltma

Tür daraltma, TypeScript'in geniş bir türü (string | number, unknown, ayrımcılı union) koşullu bir blok içinde belirli bir türe dönüştürmesinin yoludur. TypeScript daraltma belgeleri her guard'ı derinlemesine ele alır — işte her gün yazacağınız desenler.

ts
// typeof — primitive narrowing
function formatValue(val: string | number | boolean): string {
  if (typeof val === 'string')  return val.trim();
  if (typeof val === 'number')  return val.toLocaleString();
  return val ? 'Yes' : 'No';
  // TypeScript knows val must be boolean here
}

// instanceof — class/object narrowing
function handleError(err: unknown): string {
  if (err instanceof Error)   return err.message;
  if (typeof err === 'string') return err;
  return 'An unknown error occurred';
}

// in operator — property existence check
type Cat = { meow(): void };
type Dog = { bark(): void };

function makeNoise(animal: Cat | Dog) {
  if ('meow' in animal) {
    animal.meow(); // TypeScript narrows to Cat
  } else {
    animal.bark(); // TypeScript narrows to Dog
  }
}

// Discriminated union narrowing (most common in component state)
type LoadState<T> =
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; message: string };

function renderState<T>(state: LoadState<T>) {
  if (state.status === 'success') {
    console.log(state.data);    // ✅ data is accessible
  } else if (state.status === 'error') {
    console.error(state.message); // ✅ message is accessible
  }
}

// Type predicates — custom type guards
function isUser(obj: unknown): obj is User {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'id' in obj &&
    'email' in obj
  );
}

// After isUser() returns true, TypeScript knows obj is User
function processPayload(payload: unknown) {
  if (isUser(payload)) {
    console.log(payload.email); // ✅ fully typed
  }
}

typeof operatörü en temel daraltma aracıdır. instanceof, in operatörü ve ayrımcı özellik kontrolleriyle birleştirildiğinde, any veya güvensiz dönüşümler kullanmadan neredeyse her daraltma senaryosunu halledebilirsiniz.

Sonuç

TypeScript'in tür sistemi, belirsiz özellikleri ezberlemek yerine temelleri iyi öğrenmeyi ödüllendirir. Literal türler ve ayrımcılı union'lar tüm çalışma zamanı hata kategorilerini ortadan kaldırır. Genericler, yeniden kullanılabilir, tür güvenli soyutlamalar yazmanızı sağlar. Partial, Pick, Omit ve ReturnType gibi yardımcı türler türlerinizi DRY tutar. Ve narrowing ile unknown, verinin dış dünyadan geldiği kenarlarda bile tür güvenliği sağlar. Tüm bunları sağlamlaştırmanın en iyi yolu etkileşimli deney yapmaktır — TypeScript Playground, kurulum gerektirmeden herhangi bir kod parçası yapıştırıp çıkarılan türleri anında görmenizi sağlar. TypeScript projelerinde JSON verileriyle çalışırken, bu sitedeki JSON Formatter ve JSON to TypeScript araçları, gerçek API yüklerinden otomatik olarak interface tanımları üretebilir.