Introduction
TypeScript has become a popular choice for JavaScript developers due to its ability to add static types to dynamic JavaScript code. For experienced developers, understanding TypeScript's advanced type features can greatly enhance the robustness and maintainability of their applications.
This tutorial explores TypeScript's advanced type inference and complex types, helping you write safer and more maintainable code.
Prerequisites
To follow along with this tutorial, you should have:
- A solid understanding of JavaScript
- Some experience with TypeScript basics (interfaces, classes, generics)
- TypeScript installed in your development environment:
npm install -g typescript
Type Inference
TypeScript’s type inference allows the compiler to determine types automatically without explicit annotations.
Variable Type Inference
let name = 'Alice'; // inferred as string
let age = 30; // inferred as number
let isDeveloper = true; // inferred as boolean
function add(a: number, b: number) {
return a + b; // inferred return type is number
}
let numbers = [1, 2, 3]; // inferred as number[]
Contextual Typing
const handler = (event: MouseEvent) => {
console.log(event.button); // inferred as MouseEvent
};
window.onclick = handler;
Advanced Types
Intersection Types
interface Person {
name: string;
age: number;
}
interface Employee {
employeeId: number;
}
type EmployeePerson = Person & Employee;
const john: EmployeePerson = {
name: 'John Doe',
age: 35,
employeeId: 1234
};
Union Types
function printId(id: number | string) {
console.log('ID:', id);
}
printId(101);
printId('ABC123');
Literal Types
type Direction = 'up' | 'down' | 'left' | 'right';
function move(direction: Direction) {
console.log('Moving', direction);
}
move('up'); // valid
move('right'); // valid
// move('forward'); // error
Mapped Types
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
interface Car {
make: string;
model: string;
year: number;
}
const myCar: Readonly<Car> = {
make: 'Toyota',
model: 'Corolla',
year: 2020
};
// myCar.make = 'Honda'; // error
Conditional Types
type IsString<T> = T extends string ? 'string' : 'not string';
type Test1 = IsString<string>; // 'string'
type Test2 = IsString<number>; // 'not string'
Template Literal Types
type Action = 'create' | 'update' | 'delete';
type Entity = 'user' | 'post';
type LogMessage = `${Action}_${Entity}`;
function logAction(action: LogMessage) {
console.log(`Logging action: ${action}`);
}
logAction('create_user'); // valid
logAction('update_post'); // valid
// logAction('read_user'); // error
Advanced Generics
Generic Constraints
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = { name: 'Alice', age: 30 };
const name = getProperty(person, 'name'); // valid
const age = getProperty(person, 'age'); // valid
// const gender = getProperty(person, 'gender'); // error
Default Generic Parameters
interface ApiResponse<T = any> {
data: T;
status: number;
error?: string;
}
const response: ApiResponse<{ userId: number }> = {
data: { userId: 1 },
status: 200
};
const defaultResponse: ApiResponse = {
data: {},
status: 200
};
Utility Types
Partial
interface User {
name: string;
email: string;
age: number;
}
function updateUser(user: User, fieldsToUpdate: Partial<User>) {
return { ...user, ...fieldsToUpdate };
}
const user: User = { name: 'Alice', email: 'alice@example.com', age: 30 };
const updatedUser = updateUser(user, { email: 'alice@newdomain.com' });
Pick
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, 'title' | 'completed'>;
const todo: TodoPreview = {
title: 'Buy groceries',
completed: false
};
Omit
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoSummary = Omit<Todo, 'description'>;
const todo: TodoSummary = {
title: 'Buy groceries',
completed: false
};
Record
type PageInfo = {
title: string;
};
type Page = 'home' | 'about' | 'contact';
const pageInfo: Record<Page, PageInfo> = {
home: { title: 'Home Page' },
about: { title: 'About Us' },
contact: { title: 'Contact Us' }
};
Conclusion
TypeScript's advanced type inference and type features allow you to write more robust, flexible, and maintainable code. In this tutorial, we covered:
- Intersection types
- Union types
- Conditional types
- Advanced generics
- Template literal types
- Utility types (Partial, Pick, Omit, Record)
By leveraging these tools, you can significantly enhance the quality and safety of your TypeScript projects.
Next Steps
- Explore TypeScript features like decorators and namespaces.
- Use TypeScript with frameworks like React and Angular for type-safe development.
- Experiment with custom utility types for complex type transformations.