TypeScript Nedir?

TypeScript, Microsoft tarafından geliştirilen, JavaScript'e static type checking ekleyen bir programlama dilidir. JavaScript'in tüm özelliklerini içerir ve compile-time'da type checking yaparak runtime hatalarını önler.

TypeScript'in Avantajları

1. TypeScript Kurulumu ve Konfigürasyonu

Global Kurulum

# TypeScript compiler'ı global olarak kur
npm install -g typescript

# Version kontrol
tsc --version

# TypeScript dosyasını compile et
tsc hello.ts

# Watch mode
tsc hello.ts --watch

Proje Kurulumu

# Yeni Node.js projesi
mkdir my-ts-project
cd my-ts-project
npm init -y

# TypeScript development dependency olarak kur
npm install -D typescript @types/node

# tsconfig.json oluştur
npx tsc --init

# Development server için
npm install -D ts-node nodemon

tsconfig.json Konfigürasyonu

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020", "DOM"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "allowSyntheticDefaultImports": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

2. Temel Type System

Primitive Types

// String
let message: string = "Hello, TypeScript!";
let template: string = `Welcome ${message}`;

// Number
let integer: number = 42;
let float: number = 3.14;
let hex: number = 0xff;
let binary: number = 0b1010;

// Boolean
let isActive: boolean = true;
let isCompleted: boolean = false;

// Null ve Undefined
let nullValue: null = null;
let undefinedValue: undefined = undefined;

// Any (TypeScript avantajlarını kaybedersiniz)
let anything: any = 42;
anything = "string";
anything = true;

// Unknown (type-safe any)
let userInput: unknown;
userInput = 5;
userInput = "text";
if (typeof userInput === "string") {
    console.log(userInput.toUpperCase()); // Type guard gerekli
}

// Void (genellikle function return type)
function logMessage(): void {
    console.log("This function returns nothing");
}

// Never (asla return etmeyen functions)
function throwError(message: string): never {
    throw new Error(message);
}

Arrays ve Tuples

// Arrays
let numbers: number[] = [1, 2, 3, 4, 5];
let strings: Array<string> = ["a", "b", "c"];
let mixed: (string | number)[] = [1, "two", 3, "four"];

// Readonly arrays
let readonlyNumbers: readonly number[] = [1, 2, 3];
// readonlyNumbers.push(4); // Error!

// Tuples (fixed length arrays with specific types)
let person: [string, number] = ["John", 30];
let coordinates: [number, number, number] = [10, 20, 30];

// Named tuples
let namedTuple: [name: string, age: number] = ["Alice", 25];

// Optional tuple elements
let optionalTuple: [string, number?] = ["Bob"];

// Rest in tuples
let restTuple: [string, ...number[]] = ["values", 1, 2, 3, 4];

3. Object Types ve Interfaces

Object Type Annotations

// Inline object type
let user: { name: string; age: number; email?: string } = {
    name: "John Doe",
    age: 30
};

// Type alias
type User = {
    id: number;
    name: string;
    email: string;
    isActive?: boolean; // Optional property
    readonly createdAt: Date; // Read-only property
};

let newUser: User = {
    id: 1,
    name: "Jane Smith",
    email: "jane@example.com",
    createdAt: new Date()
};

// newUser.createdAt = new Date(); // Error: readonly property

Interfaces

// Interface definition
interface Product {
    id: number;
    name: string;
    price: number;
    category: string;
    description?: string;
    readonly sku: string;
}

// Interface implementation
const laptop: Product = {
    id: 1,
    name: "MacBook Pro",
    price: 2499,
    category: "Electronics",
    sku: "MBP-001"
};

// Interface extending
interface ElectronicProduct extends Product {
    warranty: number;
    brand: string;
}

// Multiple interface extending
interface SmartDevice extends Product {
    connectivity: string[];
    batteryLife?: number;
}

interface SmartPhone extends ElectronicProduct, SmartDevice {
    screenSize: number;
    operatingSystem: string;
}

// Interface for functions
interface Calculator {
    (a: number, b: number): number;
}

const add: Calculator = (x, y) => x + y;
const multiply: Calculator = (x, y) => x * y;

Index Signatures

// Index signature for objects with dynamic keys
interface StringDictionary {
    [key: string]: string;
}

let config: StringDictionary = {
    apiUrl: "https://api.example.com",
    version: "1.0.0",
    environment: "production"
};

// Mixed index signature
interface MixedDictionary {
    [key: string]: string | number | boolean;
    // specific properties can also be defined
    name: string;
    count: number;
}

// Numeric index signature
interface NumberArray {
    [index: number]: string;
    length: number;
}

4. Functions ve Type Signatures

Function Type Annotations

// Function declaration
function greet(name: string): string {
    return `Hello, ${name}!`;
}

// Function expression
const multiply = function(a: number, b: number): number {
    return a * b;
};

// Arrow function
const divide = (a: number, b: number): number => a / b;

// Optional parameters
function buildName(firstName: string, lastName?: string): string {
    return lastName ? `${firstName} ${lastName}` : firstName;
}

// Default parameters
function createUser(name: string, age: number = 18): User {
    return { name, age };
}

// Rest parameters
function sum(...numbers: number[]): number {
    return numbers.reduce((total, num) => total + num, 0);
}

// Function overloads
function format(value: string): string;
function format(value: number): string;
function format(value: boolean): string;
function format(value: string | number | boolean): string {
    return String(value);
}

Higher-Order Functions

// Function as parameter
function processArray<T>(
    arr: T[], 
    callback: (item: T, index: number) => T
): T[] {
    return arr.map(callback);
}

// Function as return type
function createCounter(): () => number {
    let count = 0;
    return () => ++count;
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

// Async functions
async function fetchUser(id: number): Promise<User> {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
}

// Generic function
function identity<T>(arg: T): T {
    return arg;
}

let output1 = identity<string>("hello");
let output2 = identity<number>(42);

5. Union Types ve Type Guards

Union Types

// Union type
type Status = "loading" | "success" | "error";
type ID = string | number;

function processId(id: ID): string {
    // Type narrowing required
    if (typeof id === "string") {
        return id.toUpperCase();
    }
    return id.toString();
}

// Discriminated unions
type LoadingState = { status: "loading" };
type SuccessState = { status: "success"; data: any };
type ErrorState = { status: "error"; message: string };

type ApiState = LoadingState | SuccessState | ErrorState;

function handleApiState(state: ApiState) {
    switch (state.status) {
        case "loading":
            console.log("Loading...");
            break;
        case "success":
            console.log("Data:", state.data);
            break;
        case "error":
            console.log("Error:", state.message);
            break;
        default:
            // Exhaustiveness check
            const _exhaustive: never = state;
            return _exhaustive;
    }
}

Type Guards

// typeof type guard
function processValue(value: string | number): string {
    if (typeof value === "string") {
        return value.toUpperCase(); // TypeScript knows it's string
    }
    return value.toFixed(2); // TypeScript knows it's number
}

// instanceof type guard
class Cat {
    meow() { console.log("Meow!"); }
}

class Dog {
    bark() { console.log("Woof!"); }
}

function makeSound(animal: Cat | Dog) {
    if (animal instanceof Cat) {
        animal.meow(); // TypeScript knows it's Cat
    } else {
        animal.bark(); // TypeScript knows it's Dog
    }
}

// Custom type guard
interface Fish {
    swim(): void;
}

interface Bird {
    fly(): void;
}

function isFish(pet: Fish | Bird): pet is Fish {
    return (pet as Fish).swim !== undefined;
}

function move(pet: Fish | Bird) {
    if (isFish(pet)) {
        pet.swim(); // TypeScript knows it's Fish
    } else {
        pet.fly(); // TypeScript knows it's Bird
    }
}

// 'in' operator type guard
type Vehicle = { drive(): void } | { fly(): void };

function operate(vehicle: Vehicle) {
    if ("drive" in vehicle) {
        vehicle.drive();
    } else {
        vehicle.fly();
    }
}

6. Generics

Generic Functions

// Basic generic function
function identity<T>(arg: T): T {
    return arg;
}

// Generic with constraints
interface Lengthwise {
    length: number;
}

function logAndReturn<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}

// Multiple generic parameters
function merge<T, U>(obj1: T, obj2: U): T & U {
    return { ...obj1, ...obj2 };
}

const merged = merge({ name: "John" }, { age: 30 });
// merged has type { name: string } & { age: number }

// Generic with keyof
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

const person = { name: "Alice", age: 30, city: "New York" };
const name = getProperty(person, "name"); // type: string
const age = getProperty(person, "age");   // type: number

Generic Classes

// Generic class
class Container<T> {
    private contents: T[] = [];

    add(item: T): void {
        this.contents.push(item);
    }

    get(index: number): T | undefined {
        return this.contents[index];
    }

    getAll(): T[] {
        return [...this.contents];
    }

    size(): number {
        return this.contents.length;
    }
}

const stringContainer = new Container<string>();
stringContainer.add("hello");
stringContainer.add("world");

const numberContainer = new Container<number>();
numberContainer.add(1);
numberContainer.add(2);

// Generic interface
interface Repository<T> {
    create(item: T): Promise<T>;
    findById(id: string): Promise<T | null>;
    findAll(): Promise<T[]>;
    update(id: string, item: Partial<T>): Promise<T>;
    delete(id: string): Promise<void>;
}

class UserRepository implements Repository<User> {
    async create(user: User): Promise<User> {
        // Implementation
        return user;
    }
    
    async findById(id: string): Promise<User | null> {
        // Implementation
        return null;
    }
    
    // ... other methods
}

7. Advanced Types

Utility Types

interface User {
    id: number;
    name: string;
    email: string;
    password: string;
    isActive: boolean;
}

// Partial - all properties optional
type PartialUser = Partial<User>;
// equivalent to:
// {
//     id?: number;
//     name?: string;
//     email?: string;
//     password?: string;
//     isActive?: boolean;
// }

// Required - all properties required
type RequiredUser = Required<PartialUser>;

// Pick - select specific properties
type PublicUser = Pick<User, 'id' | 'name' | 'email'>;

// Omit - exclude specific properties
type UserWithoutPassword = Omit<User, 'password'>;

// Record - create type with specific keys and values
type UserRoles = Record<'admin' | 'user' | 'guest', string[]>;
const roles: UserRoles = {
    admin: ['read', 'write', 'delete'],
    user: ['read', 'write'],
    guest: ['read']
};

// Exclude/Extract with union types
type Status = 'pending' | 'approved' | 'rejected';
type ActiveStatus = Exclude<Status, 'rejected'>; // 'pending' | 'approved'
type NonActiveStatus = Extract<Status, 'rejected'>; // 'rejected'

// ReturnType - extract return type from function
function getUser(): User {
    return {} as User;
}
type UserReturnType = ReturnType<typeof getUser>; // User

// Parameters - extract parameter types
type GetUserParams = Parameters<typeof getUser>; // []

Mapped Types

// Create read-only version of type
type ReadOnly<T> = {
    readonly [P in keyof T]: T[P];
};

// Create optional version of type
type Optional<T> = {
    [P in keyof T]?: T[P];
};

// Create nullable version of type
type Nullable<T> = {
    [P in keyof T]: T[P] | null;
};

type NullableUser = Nullable<User>;

// Conditional types
type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false

// More complex conditional type
type ApiResponse<T> = T extends string 
    ? { message: T } 
    : T extends number 
    ? { code: T } 
    : { data: T };

type StringResponse = ApiResponse<string>; // { message: string }
type NumberResponse = ApiResponse<number>; // { code: number }
type ObjectResponse = ApiResponse<User>;   // { data: User }

8. Decorators

Class Decorators

// Class decorator
function Component(target: any) {
    target.prototype.render = function() {
        console.log("Rendering component...");
    };
}

@Component
class MyComponent {
    name = "MyComponent";
}

const component = new MyComponent();
(component as any).render(); // "Rendering component..."

// Decorator factory
function Entity(tableName: string) {
    return function(target: any) {
        target.prototype.tableName = tableName;
    };
}

@Entity("users")
class User {
    id: number;
    name: string;
}

Method Decorators

// Method decorator
function Log(target: any, propertyName: string, descriptor: PropertyDescriptor) {
    const method = descriptor.value;
    
    descriptor.value = function (...args: any[]) {
        console.log(`Calling ${propertyName} with args:`, args);
        const result = method.apply(this, args);
        console.log(`${propertyName} returned:`, result);
        return result;
    };
}

class Calculator {
    @Log
    add(a: number, b: number): number {
        return a + b;
    }
    
    @Log
    multiply(a: number, b: number): number {
        return a * b;
    }
}

// Property decorator
function MinLength(length: number) {
    return function (target: any, propertyName: string) {
        let value: string;
        
        const getter = () => value;
        const setter = (newValue: string) => {
            if (newValue.length < length) {
                throw new Error(`${propertyName} must be at least ${length} characters`);
            }
            value = newValue;
        };
        
        Object.defineProperty(target, propertyName, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true
        });
    };
}

class User {
    @MinLength(3)
    public name: string;
}

9. Modules ve Namespaces

ES6 Modules

// user.ts
export interface User {
    id: number;
    name: string;
    email: string;
}

export class UserService {
    private users: User[] = [];
    
    addUser(user: User): void {
        this.users.push(user);
    }
    
    getUsers(): User[] {
        return this.users;
    }
}

export const DEFAULT_USER: User = {
    id: 0,
    name: "Anonymous",
    email: "anonymous@example.com"
};

// Default export
export default class ApiClient {
    baseUrl: string;
    
    constructor(baseUrl: string) {
        this.baseUrl = baseUrl;
    }
}

// main.ts
import ApiClient, { User, UserService, DEFAULT_USER } from './user';
import * as UserModule from './user';

const client = new ApiClient('https://api.example.com');
const userService = new UserModule.UserService();

Namespaces

// shapes.ts
namespace Shapes {
    export interface Point {
        x: number;
        y: number;
    }
    
    export class Circle {
        constructor(public center: Point, public radius: number) {}
        
        area(): number {
            return Math.PI * this.radius ** 2;
        }
    }
    
    export class Rectangle {
        constructor(
            public topLeft: Point, 
            public width: number, 
            public height: number
        ) {}
        
        area(): number {
            return this.width * this.height;
        }
    }
}

// Usage
const circle = new Shapes.Circle({ x: 0, y: 0 }, 10);
const rectangle = new Shapes.Rectangle({ x: 0, y: 0 }, 20, 30);

10. Best Practices

Type Safety Best Practices

// ✅ Use strict TypeScript configuration
// tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "noUnusedLocals": true
  }
}

// ✅ Use type assertions carefully
// Bad
const element = document.getElementById('my-element') as HTMLInputElement;

// Good
const element = document.getElementById('my-element');
if (element instanceof HTMLInputElement) {
    element.value = 'typed value';
}

// ✅ Use type guards for runtime checks
function isString(value: unknown): value is string {
    return typeof value === 'string';
}

// ✅ Prefer interfaces for object shapes
interface UserConfig {
    apiUrl: string;
    timeout: number;
    retries?: number;
}

// ✅ Use enums for related constants
enum HttpStatus {
    OK = 200,
    NotFound = 404,
    InternalServerError = 500
}

// ✅ Use readonly for immutable data
interface ReadonlyConfig {
    readonly apiUrl: string;
    readonly features: readonly string[];
}

Performance ve Organization

// ✅ Use index signatures efficiently
interface Config {
    [key: string]: string | number | boolean;
    // Known properties for better type checking
    apiUrl: string;
    timeout: number;
}

// ✅ Organize types in separate files
// types/user.ts
export interface User {
    id: string;
    name: string;
    email: string;
}

export type UserRole = 'admin' | 'user' | 'guest';

// types/api.ts
export interface ApiResponse<T> {
    data: T;
    status: number;
    message?: string;
}

// types/index.ts
export * from './user';
export * from './api';

// ✅ Use barrel exports
// index.ts
export { UserService } from './services/UserService';
export { ApiClient } from './clients/ApiClient';
export * from './types';

11. Real-World Examples

Express.js with TypeScript

// types.ts
export interface CreateUserRequest {
    name: string;
    email: string;
    password: string;
}

export interface User {
    id: string;
    name: string;
    email: string;
    createdAt: Date;
}

// userController.ts
import { Request, Response } from 'express';
import { CreateUserRequest, User } from './types';

export class UserController {
    async createUser(
        req: Request<{}, User, CreateUserRequest>, 
        res: Response<User | { error: string }>
    ): Promise<void> {
        try {
            const { name, email, password } = req.body;
            
            const user: User = {
                id: generateId(),
                name,
                email,
                createdAt: new Date()
            };
            
            // Save user logic...
            
            res.status(201).json(user);
        } catch (error) {
            res.status(400).json({ error: error.message });
        }
    }
}

// app.ts
import express from 'express';
import { UserController } from './userController';

const app = express();
const userController = new UserController();

app.use(express.json());
app.post('/users', userController.createUser.bind(userController));

export default app;

React with TypeScript

// components/UserCard.tsx
import React from 'react';

interface UserCardProps {
    user: {
        id: string;
        name: string;
        email: string;
        avatar?: string;
    };
    onEdit: (userId: string) => void;
    onDelete: (userId: string) => void;
    className?: string;
}

export const UserCard: React.FC<UserCardProps> = ({ 
    user, 
    onEdit, 
    onDelete, 
    className 
}) => {
    const handleEdit = () => onEdit(user.id);
    const handleDelete = () => onDelete(user.id);
    
    return (
        <div className={`user-card ${className || ''}`}>
            {user.avatar && (
                <img src={user.avatar} alt={`${user.name}'s avatar`} />
            )}
            <h3>{user.name}</h3>
            <p>{user.email}</p>
            <div className="actions">
                <button onClick={handleEdit}>Edit</button>
                <button onClick={handleDelete}>Delete</button>
            </div>
        </div>
    );
};

// hooks/useUsers.ts
import { useState, useEffect } from 'react';

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

export const useUsers = () => {
    const [users, setUsers] = useState<User[]>([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState<string | null>(null);
    
    useEffect(() => {
        fetchUsers();
    }, []);
    
    const fetchUsers = async (): Promise<void> => {
        try {
            setLoading(true);
            const response = await fetch('/api/users');
            if (!response.ok) {
                throw new Error('Failed to fetch users');
            }
            const userData: User[] = await response.json();
            setUsers(userData);
        } catch (err) {
            setError(err instanceof Error ? err.message : 'Unknown error');
        } finally {
            setLoading(false);
        }
    };
    
    return { users, loading, error, refetch: fetchUsers };
};

Sonuç

TypeScript modern web development'ın vazgeçilmez bir parçası haline gelmiştir. Bu rehberde öğrendiklerinizi özetlersek:

Ana Faydalar:

Best Practices:

TypeScript öğrenmek için en iyi yol mevcut JavaScript projelerinizi kademeli olarak TypeScript'e migrate etmektir. Başlangıçta basic type annotations kullanarak başlayın ve zamanla advanced features'ları projenize entegre edin.