TypeScript for JavaScript Developers: A Practical Guide
Asad Ali
Founder & Lead Developer · Former WordPress Core Contributor
TypeScript for JavaScript Developers: A Practical Guide
TypeScript is JavaScript with types. It catches bugs at compile time instead of runtime, and makes your IDE incredibly helpful.
Why TypeScript?
The JavaScript Problem
function getUser(id) {
// What is id? string? number?
// What does this return?
}
// Later...
const user = getUser("abc");
console.log(user.fullName); // Runtime error: user.name, not fullName
You only find the bug when the code runs.
The TypeScript Solution
interface User {
id: string;
name: string;
email: string;
}
function getUser(id: string): User {
// TypeScript knows what id is and what we return
}
const user = getUser("abc");
console.log(user.fullName); // Error caught immediately in IDE
Bugs are caught before your code runs.
Getting Started
Setup
npm install typescript --save-dev
npx tsc --init
Or just use a framework that includes it:
npx create-next-app@latest my-app --typescript
File Extension
Use .ts for TypeScript files, .tsx for TypeScript with JSX (React).
Basic Types
Primitive Types
// String
let name: string = "John";
// Number
let age: number = 30;
// Boolean
let isActive: boolean = true;
// Null and Undefined
let nothing: null = null;
let notDefined: undefined = undefined;
Arrays
// Array of strings
let names: string[] = ["Alice", "Bob"];
// Array of numbers
let scores: number[] = [100, 95, 88];
// Alternative syntax
let items: Array = ["a", "b", "c"];
Objects
// Inline type
let user: { name: string; age: number } = {
name: "John",
age: 30,
};
// Better: use interface (see below)
Interfaces
Define the shape of objects:
interface User {
id: string;
name: string;
email: string;
age?: number; // Optional property (?)
readonly createdAt: Date; // Can't be changed
}
const user: User = {
id: "1",
name: "John",
email: "john@example.com",
createdAt: new Date(),
};
user.name = "Jane"; // OK
user.createdAt = new Date(); // Error: readonly
Extending Interfaces
interface Person {
name: string;
age: number;
}
interface Employee extends Person {
employeeId: string;
department: string;
}
const employee: Employee = {
name: "John",
age: 30,
employeeId: "E001",
department: "Engineering",
};
Type Aliases
Similar to interfaces but can define any type:
// Union type
type Status = "pending"
"approved"
"rejected";
// Object type
type Point = {
x: number;
y: number;
};
// Function type
type Callback = (data: string) => void;
Interface vs Type
| Feature | Interface | Type |
| Object shapes | ✅ | ✅ |
| Extending | ✅ | ✅ (with &) |
| Union types | ❌ | ✅ |
| Declaration merging | ✅ | ❌ |
Rule of thumb: Use interface for objects, type for everything else.
Functions
Typed Parameters and Return
function greet(name: string): string {
return Hello, ${name}!;
}
// Arrow function
const add = (a: number, b: number): number => a + b;
Optional and Default Parameters
function greet(name: string, greeting?: string): string {
return ${greeting || "Hello"}, ${name}!;
}
function greetWithDefault(name: string, greeting: string = "Hello"): string {
return ${greeting}, ${name}!;
}
Function Types
type MathOperation = (a: number, b: number) => number;
const add: MathOperation = (a, b) => a + b;
const multiply: MathOperation = (a, b) => a * b;
Union Types
A value can be one of several types:
type StringOrNumber = string | number;
function printId(id: string | number) {
console.log(ID: ${id});
}
printId("abc"); // OK
printId(123); // OK
printId(true); // Error
Narrowing
TypeScript narrows types based on checks:
function printId(id: string | number) {
if (typeof id === "string") {
// TypeScript knows id is string here
console.log(id.toUpperCase());
} else {
// TypeScript knows id is number here
console.log(id.toFixed(2));
}
}
Generics
Make reusable types flexible:
// Generic function
function identity(value: T): T {
return value;
}
identity("hello"); // Returns string
identity(42); // Returns number
// TypeScript infers the type
identity("hello"); // Inferred as string
Generic Interfaces
interface ApiResponse {
data: T;
status: number;
message: string;
}
interface User {
id: string;
name: string;
}
// Response with User data
type UserResponse = ApiResponse;
const response: UserResponse = {
data: { id: "1", name: "John" },
status: 200,
message: "Success",
};
Generic Constraints
interface HasId {
id: string;
}
// T must have an id property
function getById(items: T[], id: string): T | undefined {
return items.find(item => item.id === id);
}
Practical React Examples
Typed Props
interface ButtonProps {
label: string;
onClick: () => void;
variant?: "primary" | "secondary";
disabled?: boolean;
}
function Button({ label, onClick, variant = "primary", disabled }: ButtonProps) {
return (
);
}
Typed State
interface User {
id: string;
name: string;
email: string;
}
function UserProfile() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// TypeScript knows user might be null
return (
{user?.name || "Loading..."}
);
}
Event Handlers
function Form() {
const handleChange = (event: React.ChangeEvent) => {
console.log(event.target.value);
};
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
};
return (
);
}
Common Patterns
API Responses
interface ApiError {
message: string;
code: number;
}
type ApiResponse =
| { success: true; data: T }
| { success: false; error: ApiError };
async function fetchUser(id: string): Promise> {
try {
const response = await fetch(/api/users/${id});
const data = await response.json();
return { success: true, data };
} catch (error) {
return { success: false, error: { message: "Failed", code: 500 } };
}
}
// Usage
const result = await fetchUser("1");
if (result.success) {
console.log(result.data.name); // TypeScript knows data exists
} else {
console.log(result.error.message); // TypeScript knows error exists
}
Props with Children
interface CardProps {
title: string;
children: React.ReactNode;
}
function Card({ title, children }: CardProps) {
return (
{title}
{children}
);
}
Object Keys
interface Theme {
primary: string;
secondary: string;
accent: string;
}
const theme: Theme = {
primary: "#3b82f6",
secondary: "#6b7280",
accent: "#10b981",
};
// Type-safe key access
type ThemeKey = keyof Theme; // "primary"
"secondary"
"accent"
function getColor(key: ThemeKey): string {
return theme[key];
}
Migration Tips
Gradual Adoption
1. Rename .js to .ts
2. Add types where TypeScript complains
3. Enable stricter options over time
tsconfig.json Settings
Start lenient:
{
"compilerOptions": {
"strict": false,
"noImplicitAny": false
}
}
Move toward strict:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}
Using any (Temporarily)
When stuck, use any and fix later:
// TODO: Type this properly
const data: any = fetchSomething();
But mark it to fix later. Too much any defeats the purpose.
Common Errors
"Object is possibly undefined"
// Error
const user: User | undefined = getUser();
console.log(user.name); // Error!
// Fix 1: Check first
if (user) {
console.log(user.name);
}
// Fix 2: Optional chaining
console.log(user?.name);
// Fix 3: Non-null assertion (when you're sure)
console.log(user!.name);
"Type 'X' is not assignable to type 'Y'"
interface User {
name: string;
}
// Error: missing required property
const user: User = {}; // Error!
// Fix
const user: User = { name: "John" };
"Argument of type 'X' is not assignable"
function greet(name: string) {}
greet(123); // Error!
greet("John"); // OK
Tools and Resources
- VS Code - Best TypeScript support
- TypeScript Playground - typescriptlang.org/play
- Official Docs - typescriptlang.org/docs
- Type Challenges - GitHub
Conclusion
TypeScript benefits:
| Aspect | Without TS | With TS |
| Bug detection | Runtime | Compile time |
| IDE support | Basic | Excellent |
| Refactoring | Scary | Safe |
| Documentation | Comments | Types |
| Team onboarding | Read all code | Types explain themselves |
Start by adding types to function parameters and return values. As you get comfortable, enable stricter settings.
Related guides:
Related Articles
View allGit for WordPress Developers: Getting Started
Learn Git version control coming from WordPress. Track changes, collaborate, and never lose work again.
Tailwind CSS for WordPress Developers: Complete Guide
Learn Tailwind CSS coming from WordPress. Utility-first CSS explained for theme developers making the switch to modern frameworks.
React vs Vue vs Svelte for WordPress Developers
Confused by JavaScript frameworks? This guide explains React, Vue, and Svelte from a WordPress developer's perspective.
MDX for Beginners: Write Content Like a Developer
Learn how MDX combines Markdown with React components. Perfect for developers migrating from WordPress to modern content workflows.