TypeScript has become an essential tool for modern web development. This guide will help JavaScript developers transition to TypeScript and leverage its powerful type system.
Why TypeScript?
JavaScript's dynamic typing can lead to runtime errors that could have been caught during development. TypeScript adds static typing, providing:
- Early error detection
- Better IDE support and autocompletion
- Improved code maintainability
- Self-documenting code
- Better refactoring capabilities
Basic Types
// Basic types
let name: string = "Mohamed";
let age: number = 28;
let isDeveloper: boolean = true;
let skills: string[] = ["React", "Node.js", "TypeScript"];
let nullable: null = null;
let unknownType: unknown = "could be anything";
// Any (use sparingly)
let flexible: any = 42;
flexible = "now a string";
// Void (for functions that don't return)
function logMessage(message: string): void {
console.log(message);
}
Interfaces and Custom Types
// Interface for objects
interface User {
id: number;
name: string;
email: string;
age?: number; // Optional property
readonly createdAt: Date; // Read-only
}
// Type alias
type UserRole = 'admin' | 'user' | 'guest';
// Extending interfaces
interface Admin extends User {
permissions: string[];
role: 'admin';
}
// Using interfaces
const user: User = {
id: 1,
name: "Mohamed Saber",
email: "mohamed@example.com",
createdAt: new Date()
};
Generics
Generics allow you to create reusable components that work with multiple types:
// Generic function
function identity<T>(value: T): T {
return value;
}
let result1 = identity<string>("hello");
let result2 = identity<number>(42);
// Generic interface
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
type UserResponse = ApiResponse<User>;
// Generic constraints
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
Advanced Types
// Union types
type ID = string | number;
// Intersection types
type PersonWithAddress = User & {
address: string;
city: string;
};
// Utility types
type PartialUser = Partial<User>; // All properties optional
type RequiredUser = Required<User>; // All properties required
type ReadonlyUser = Readonly<User>; // All properties read-only
type UserWithoutId = Omit<User, 'id'>; // Exclude id property
type UserNames = Pick<User, 'name' | 'email'>; // Only name and email
Working with React and TypeScript
// Component props interface
interface ButtonProps {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
}
// React component with TypeScript
const Button: React.FC<ButtonProps> = ({
label,
onClick,
variant = 'primary',
disabled = false
}) => {
return (
<button onClick={onClick} disabled={disabled}>
{label}
</button>
);
};
// useState with TypeScript
const [user, setUser] = useState<User | null>(null);
// useReducer with TypeScript
type State = { count: number };
type Action = { type: 'increment' } | { type: 'decrement' };
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
};
Type Guards
// Type guards using typeof
function processValue(value: string | number) {
if (typeof value === 'string') {
return value.toUpperCase();
}
return value.toFixed(2);
}
// Custom type guard
function isUser(obj: any): obj is User {
return obj && typeof obj.id === 'number' && typeof obj.name === 'string';
}
// Discriminated unions
interface Circle {
kind: 'circle';
radius: number;
}
interface Square {
kind: 'square';
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'square':
return shape.sideLength ** 2;
}
}
Best Practices
- Avoid using 'any' - Use 'unknown' or proper types instead
- Use readonly for unchanged properties - Prevents accidental mutations
- Prefer interfaces over type aliases for objects - They're more extendable
- Use strict mode - Enable strict: true in tsconfig.json
- Don't overuse generics - Only when you need type flexibility
Conclusion
TypeScript significantly improves JavaScript development by adding type safety. While there's a learning curve, the benefits of catching errors early, better tooling, and improved code maintainability make it worth the investment. Start with small projects and gradually adopt TypeScript in your workflow.

