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
- Catching runtime errors at compile time
- Fewer production bugs
- Smart code completion in the IDE
- Safer refactoring
2. Code Quality
- Self-documenting code
- A more readable codebase
- Less time spent on code review
- Automatic documentation generation
3. Developer Experience
- IntelliSense support
- Warnings before errors are even caught
- Easier navigation in large projects
- Improved team collaboration
Why It Matters
- TypeScript is one of the most preferred languages among developers
- Its adoption has grown steadily year over year
- A large share of major companies rely on it
- It shortens bug-fixing cycles
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:
- Using project references for faster builds
- Incremental compilation for quicker rebuilds
- Type checking parallelization
Bundle Size:
- Eliminating unused code with tree shaking
- Type-only imports:
import type { User } from './types' - No runtime overhead (types compile away)
Team Collaboration
1. Code Review Benefits
- Type errors caught before runtime, reducing bugs
- Self-documenting code shortens review time
- Standardized patterns speed up onboarding
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:
- 100% type coverage
- Strict mode enabled
- ESLint + Prettier configuration
- Automated testing with Jest
- CI/CD type checking
Results:
- Noticeably fewer production bugs
- Faster development cycles
- Higher overall code quality
Contact
For professional web applications built with TypeScript:
Phone: +90 536 628 0007
Email: info@enextware.com