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.
// 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 patternTypeScript'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:
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 & {});(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.
// 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.
// 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.
// 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:
// 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 Useras 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:
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 syncunknown 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ı.
// 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.
// 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.