TypeScript has become an indispensable tool in modern web development. In this article, we'll take a detailed look at why TypeScript matters so much and how to use it effectively.
What Is TypeScript and Why Should We Use It?
Core Advantages
1. Type Safety
2. Code Quality
3. Developer Experience
Why It Matters
TypeScript Fundamentals
1. Basic Types and Interfaces
// Primitive types
let username: string = "Erdenay";
let age: number = 25;
let isActive: boolean = true;
// Arrays
let tags: string[] = ["web", "design", "seo"];
let scores: Array<number> = [95, 87, 92];
// Tuples
let user: [string, number, boolean] = ["Erdenay", 25, true];
// Enums
enum UserRole {
Admin = "ADMIN",
User = "USER",
Guest = "GUEST"
}
// Interfaces
interface Product {
id: string;
name: string;
price: number;
inStock: boolean;
category?: string; // Optional
readonly createdAt: Date; // Readonly
}
// Type aliases
type ID = string | number;
type Status = "pending" | "approved" | "rejected";
// Union types
function printId(id: string | number) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id.toFixed(2));
}
}2. Advanced Types
// Generics
function getFirstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
const firstNumber = getFirstElement([1, 2, 3]); // number
const firstString = getFirstElement(["a", "b"]); // string
// Utility Types
interface User {
id: string;
name: string;
email: string;
age: number;
}
// Partial - Makes all properties optional
type PartialUser = Partial<User>;
// Pick - Select specific properties
type UserPreview = Pick<User, "id" | "name">;
// Omit - Exclude specific properties
type UserWithoutEmail = Omit<User, "email">;
// Record - Create object type with specific keys
type UserRoles = Record<string, UserRole>;
// Mapped Types
type ReadonlyUser = {
readonly [K in keyof User]: User[K];
};
// Conditional Types
type IsString<T> = T extends string ? "yes" : "no";
type Test1 = IsString<string>; // "yes"
type Test2 = IsString<number>; // "no"3. Function Types
// Function type annotations
function add(a: number, b: number): number {
return a + b;
}
// Arrow function with types
const multiply = (a: number, b: number): number => a * b;
// Optional and default parameters
function greet(name: string, greeting: string = "Hello"): string {
return `${greeting}, ${name}!`;
}
// Rest parameters
function sum(...numbers: number[]): number {
return numbers.reduce((acc, num) => acc + num, 0);
}
// Function overloads
function parseValue(value: string): string[];
function parseValue(value: number): number[];
function parseValue(value: string | number): string[] | number[] {
if (typeof value === "string") {
return value.split(",");
}
return [value];
}TypeScript Best Practices with React
1. Component Types
import { FC, ReactNode } from 'react';
// Props interface
interface ButtonProps {
children: ReactNode;
onClick: () => void;
variant?: "primary" | "secondary";
disabled?: boolean;
}
// Functional Component with types
const Button: FC<ButtonProps> = ({
children,
onClick,
variant = "primary",
disabled = false
}) => {
return (
<button
onClick={onClick}
disabled={disabled}
className={`btn btn-${variant}`}
>
{children}
</button>
);
};
// Generic component
interface ListProps<T> {
items: T[];
renderItem: (item: T) => ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{renderItem(item)}</li>
))}
</ul>
);
}2. Hooks with TypeScript
import { useState, useEffect, useCallback, useMemo } from 'react';
// useState with explicit type
const [user, setUser] = useState<User | null>(null);
const [count, setCount] = useState<number>(0);
// useEffect
useEffect(() => {
const fetchUser = async () => {
const response = await fetch('/api/user');
const data: User = await response.json();
setUser(data);
};
fetchUser();
}, []);
// useCallback
const handleClick = useCallback((id: string) => {
console.log(`Clicked: ${id}`);
}, []);
// useMemo
const expensiveValue = useMemo(() => {
return computeExpensiveValue(items);
}, [items]);
// Custom hook with types
function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue] as const;
}3. Event Handlers
import { ChangeEvent, FormEvent, MouseEvent } from 'react';
function Form() {
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
// Handle form submission
};
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
console.log("Button clicked");
};
return (
<form onSubmit={handleSubmit}>
<input type="text" onChange={handleChange} />
<button onClick={handleClick}>Submit</button>
</form>
);
}TypeScript with Next.js
1. Page Components
// app/products/[id]/page.tsx
import type { Metadata } from 'next';
interface PageProps {
params: {
id: string;
};
searchParams: {
[key: string]: string | string[] | undefined;
};
}
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const product = await fetchProduct(params.id);
return {
title: product.name,
description: product.description,
};
}
export default async function ProductPage({ params, searchParams }: PageProps) {
const product = await fetchProduct(params.id);
return <div>{product.name}</div>;
}2. API Routes
// app/api/products/route.ts
import { NextRequest, NextResponse } from 'next/server';
interface Product {
id: string;
name: string;
price: number;
}
export async function GET(request: NextRequest) {
const products: Product[] = await fetchProducts();
return NextResponse.json(products);
}
export async function POST(request: NextRequest) {
const body: Partial<Product> = await request.json();
const newProduct = await createProduct(body);
return NextResponse.json(newProduct, { status: 201 });
}Enterprise Patterns
1. Domain-Driven Design
// types/domain/product.ts
export interface ProductEntity {
id: ProductId;
name: ProductName;
price: Money;
stock: Stock;
}
export type ProductId = string & { readonly __brand: "ProductId" };
export type ProductName = string & { readonly __brand: "ProductName" };
export class Money {
private constructor(
public readonly amount: number,
public readonly currency: string
) {}
static create(amount: number, currency: string): Money {
if (amount < 0) throw new Error("Amount cannot be negative");
return new Money(amount, currency);
}
}2. Repository Pattern
interface Repository<T> {
findById(id: string): Promise<T | null>;
findAll(): Promise<T[]>;
create(entity: Omit<T, 'id'>): Promise<T>;
update(id: string, entity: Partial<T>): Promise<T>;
delete(id: string): Promise<void>;
}
class ProductRepository implements Repository<Product> {
async findById(id: string): Promise<Product | null> {
// Implementation
}
// ... other methods
}Performance and Optimization
1. Strict Mode Configuration
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"esModuleInterop": true,
"moduleResolution": "bundler",
"target": "ES2022",
"lib": ["ES2022", "DOM"],
"jsx": "preserve",
"incremental": true
}
}2. Build Optimization
Compile Time:
Bundle Size:
Team Collaboration
1. Code Review Benefits
2. Documentation
/**
* Calculates the total price of items in cart
* @param items - Array of cart items
* @param discountCode - Optional discount code
* @returns Total price after discounts
* @throws {Error} If discount code is invalid
* @example
* ```ts
* const total = calculateTotal(cartItems, "SUMMER25");
* ```
*/
function calculateTotal(
items: CartItem[],
discountCode?: string
): number {
// Implementation
}Enextware TypeScript Standards
We use TypeScript across all of our projects:
Results:
Contact
For professional web applications built with TypeScript:
Phone: +90 536 628 0007
Email: info@enextware.com
