TypeScript Essentials
JavaScript with Types
Explanation
What is TypeScript?
TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. It adds optional static typing, classes, and modules to help build robust applications.
Benefits
| Benefit | Description | |---------|-------------| | Type Safety | Catch errors at compile time | | Better IDE Support | Autocomplete, refactoring | | Documentation | Types document code | | Scalability | Easier to maintain large codebases |
Demonstration
Example 1: Basic Types
// Primitive types
const name: string = 'Arthur';
const age: number = 30;
const isActive: boolean = true;
const nothing: null = null;
const notDefined: undefined = undefined;
// Arrays
const numbers: number[] = [1, 2, 3];
const strings: Array<string> = ['a', 'b', 'c'];
// Tuples
const tuple: [string, number] = ['hello', 42];
const [greeting, count] = tuple;
// Enums
enum Status {
Pending,
Active,
Completed
}
const status: Status = Status.Active;
// String enums
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}
// Any (avoid if possible)
let anything: any = 'hello';
anything = 42;
// Unknown (safer than any)
let unknown: unknown = 'hello';
if (typeof unknown === 'string') {
console.log(unknown.toUpperCase()); // Type guard
}
// Void and never
function log(message: string): void {
console.log(message);
}
function throwError(message: string): never {
throw new Error(message);
}
// Literal types
type Theme = 'light' | 'dark';
const theme: Theme = 'light';
// Type assertions
const input = document.getElementById('input') as HTMLInputElement;
const input2 = <HTMLInputElement>document.getElementById('input');
Example 2: Objects and Interfaces
// Object type
const user: { name: string; age: number } = {
name: 'Arthur',
age: 30
};
// Interface
interface User {
id: number;
name: string;
email: string;
age?: number; // Optional
readonly createdAt: Date; // Read-only
}
const user1: User = {
id: 1,
name: 'Arthur',
email: 'art@bpc.com',
createdAt: new Date()
};
// Extending interfaces
interface Admin extends User {
role: 'admin';
permissions: string[];
}
// Interface for functions
interface GreetFunction {
(name: string): string;
}
const greet: GreetFunction = (name) => `Hello, ${name}!`;
// Index signatures
interface Dictionary {
[key: string]: string;
}
const dict: Dictionary = {
hello: 'world',
foo: 'bar'
};
// Type aliases
type ID = string | number;
type Point = { x: number; y: number };
// Intersection types
type Employee = User & {
department: string;
salary: number;
};
// Type vs Interface
// - Interfaces can be extended and merged
// - Types can use unions and mapped types
// - Use interfaces for objects, types for everything else
Example 3: Functions
// Function types
function add(a: number, b: number): number {
return a + b;
}
// Arrow function
const multiply = (a: number, b: number): number => a * b;
// Optional parameters
function greet(name: string, greeting?: string): string {
return `${greeting || 'Hello'}, ${name}!`;
}
// Default parameters
function createUser(name: string, role: string = 'user'): User {
return { name, role };
}
// Rest parameters
function sum(...numbers: number[]): number {
return numbers.reduce((a, b) => a + b, 0);
}
// Function overloading
function format(value: string): string;
function format(value: number): string;
function format(value: string | number): string {
if (typeof value === 'string') {
return value.toUpperCase();
}
return value.toFixed(2);
}
// Generic functions
function identity<T>(value: T): T {
return value;
}
const str = identity<string>('hello');
const num = identity(42); // Type inferred
// Generic constraints
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: 'Arthur', age: 30 };
const name = getProperty(user, 'name'); // string
Example 4: Generics
// Generic interface
interface Response<T> {
data: T;
status: number;
message: string;
}
interface User {
id: number;
name: string;
}
const userResponse: Response<User> = {
data: { id: 1, name: 'Arthur' },
status: 200,
message: 'Success'
};
// Generic class
class Container<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
setValue(value: T): void {
this.value = value;
}
}
const numberContainer = new Container<number>(42);
const stringContainer = new Container('hello');
// Generic constraints
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(value: T): void {
console.log(value.length);
}
logLength('hello'); // OK
logLength([1, 2, 3]); // OK
// logLength(42); // Error: number has no length
// Multiple type parameters
function map<T, U>(array: T[], fn: (item: T) => U): U[] {
return array.map(fn);
}
const numbers = [1, 2, 3];
const strings = map(numbers, n => n.toString());
// Default type parameters
interface Pagination<T = any> {
items: T[];
page: number;
total: number;
}
Example 5: Utility Types
interface User {
id: number;
name: string;
email: string;
age: number;
role: 'admin' | 'user';
}
// Partial - all properties optional
type PartialUser = Partial<User>;
const update: PartialUser = { name: 'New Name' };
// Required - all properties required
type RequiredUser = Required<User>;
// Readonly - all properties readonly
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = { id: 1, name: 'Arthur', /* ... */ };
// user.name = 'New'; // Error!
// Pick - select properties
type UserPreview = Pick<User, 'id' | 'name'>;
// Omit - exclude properties
type UserWithoutId = Omit<User, 'id'>;
// Record - create object type
type RolePermissions = Record<User['role'], string[]>;
const permissions: RolePermissions = {
admin: ['read', 'write', 'delete'],
user: ['read']
};
// Extract - extract from union
type AdminRole = Extract<User['role'], 'admin'>;
// Exclude - exclude from union
type NonAdminRole = Exclude<User['role'], 'admin'>;
// NonNullable
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
// ReturnType
function getUser() {
return { id: 1, name: 'Arthur' };
}
type UserFromFunction = ReturnType<typeof getUser>;
// Parameters
function createUser(name: string, age: number): User { /* ... */ }
type CreateUserParams = Parameters<typeof createUser>; // [string, number]
// Awaited (for Promise types)
type ResolvedUser = Awaited<Promise<User>>; // User
Example 6: Advanced Patterns
// Discriminated unions
interface Circle {
kind: 'circle';
radius: number;
}
interface Rectangle {
kind: 'rectangle';
width: number;
height: number;
}
type Shape = Circle | Rectangle;
function getArea(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'rectangle':
return shape.width * shape.height;
}
}
// Type guards
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function process(value: string | number) {
if (isString(value)) {
console.log(value.toUpperCase()); // TypeScript knows it's string
} else {
console.log(value.toFixed(2)); // TypeScript knows it's number
}
}
// Mapped types
type Nullable<T> = { [K in keyof T]: T[K] | null };
type NullableUser = Nullable<User>;
// Template literal types
type EventName = `on${Capitalize<string>}`;
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Endpoint = `/${string}`;
type Route = `${HttpMethod} ${Endpoint}`;
// Conditional types
type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
// infer keyword
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Unwrapped = UnwrapPromise<Promise<string>>; // string
// Const assertions
const config = {
endpoint: '/api',
timeout: 5000
} as const;
// config.endpoint is now '/api', not string
Key Takeaways:
- Types catch errors early
- Use interfaces for objects
- Generics for reusable code
- Utility types save time
- Type guards for runtime checks
Imitation
Challenge 1: Type a REST API Client
Task: Create types for a generic API client.
Solution
// API types
interface ApiResponse<T> {
data: T;
meta: {
timestamp: string;
requestId: string;
};
}
interface ApiError {
code: string;
message: string;
details?: Record<string, string[]>;
}
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
interface RequestConfig {
headers?: Record<string, string>;
params?: Record<string, string | number>;
timeout?: number;
}
// API Client
class ApiClient {
constructor(private baseUrl: string) {}
private async request<T>(
method: HttpMethod,
path: string,
data?: unknown,
config?: RequestConfig
): Promise<ApiResponse<T>> {
const url = new URL(path, this.baseUrl);
if (config?.params) {
Object.entries(config.params).forEach(([key, value]) => {
url.searchParams.set(key, String(value));
});
}
const response = await fetch(url.toString(), {
method,
headers: {
'Content-Type': 'application/json',
...config?.headers
},
body: data ? JSON.stringify(data) : undefined
});
if (!response.ok) {
const error: ApiError = await response.json();
throw error;
}
return response.json();
}
get<T>(path: string, config?: RequestConfig) {
return this.request<T>('GET', path, undefined, config);
}
post<T, D = unknown>(path: string, data: D, config?: RequestConfig) {
return this.request<T>('POST', path, data, config);
}
put<T, D = unknown>(path: string, data: D, config?: RequestConfig) {
return this.request<T>('PUT', path, data, config);
}
delete<T>(path: string, config?: RequestConfig) {
return this.request<T>('DELETE', path, undefined, config);
}
}
// Usage
interface User {
id: number;
name: string;
email: string;
}
const api = new ApiClient('https://api.example.com');
const { data: user } = await api.get<User>('/users/1');
const { data: newUser } = await api.post<User>('/users', {
name: 'Arthur',
email: 'art@bpc.com'
});
Practice
Exercise 1: Type a Form Handler
Difficulty: Intermediate
Create types for a type-safe form handling system.
Exercise 2: Build a Type-Safe Event Emitter
Difficulty: Advanced
Create an event emitter with typed events.
Summary
What you learned:
- Basic and advanced types
- Interfaces and type aliases
- Generics and constraints
- Utility types
- Type guards and narrowing
Next Steps:
- Read: Testing
- Practice: Migrate a JS project
- Explore: Strict mode, declaration files
