Generics
## Learning Objectives
- Create generic functions
- Define generic classes
- Use generic interfaces
- Apply type constraints
- Master utility types
## Why Generics?
### Without Generics
```typescript
function identity(arg: any): any {
return arg;
}
let num = identity(42); // type: any
let str = identity("hello"); // type: any
// Lost type information
```
### With Generics
```typescript
function identity(arg: T): T {
return arg;
}
let num = identity(42); // type: number
let str = identity("hello"); // type: string
// Type information preserved
```
## Generic Functions
### Syntax
```typescript
function firstElement(arr: T[]): T | undefined {
return arr[0];
}
let num = firstElement([1, 2, 3]); // number | undefined
let str = firstElement(["a", "b", "c"]); // string | undefined
```
### Multiple Type Parameters
```typescript
function pair(first: T, second: U): [T, U] {
return [first, second];
}
let p = pair("hello", 42); // [string, number]
```
## Generic Interfaces
### Declaration
```typescript
interface Container {
value: T;
getValue(): T;
}
class Box implements Container {
constructor(public value: T) {}
getValue(): T {
return this.value;
}
}
let stringBox = new Box("hello");
let numBox = new Box(42);
```
### Generic Interface Multiple Type Parameters
```typescript
interface Dictionary {
key: T;
value: U;
}
let dict: Dictionary = { key: "age", value: 30 };
```
## Generic Classes
### Basic Syntax
```typescript
class DataStore {
private data: T[] = [];
add(item: T): void {
this.data.push(item);
}
getAll(): T[] {
return [...this.data];
}
getByIndex(index: number): T | undefined {
return this.data[index];
}
}
let stringStore = new DataStore();
stringStore.add("hello");
stringStore.add("world");
```
### Generic Class Multiple Type Parameters
```typescript
class Pair {
constructor(
public key: K,
public value: V
) {}
toString(): string {
return `${this.key}: ${this.value}`;
}
}
let pair = new Pair("age", 30);
pair.toString(); // "age: 30"
```
## Generic Constraints
### Using extends
```typescript
interface HasLength {
length: number;
}
function logLength(arg: T): T {
console.log(arg.length);
return arg;
}
logLength("hello"); // 5
logLength([1, 2, 3]); // 3
logLength({ length: 10 }); // 10
// logLength(123); // Error: number has no length
```
### Constrain to Object Keys
```typescript
function getProperty(obj: T, key: K): T[K] {
return obj[key];
}
let person = { name: "Alice", age: 30, city: "NYC" };
getProperty(person, "name"); // string
getProperty(person, "age"); // number
// getProperty(person, "foo"); // Error
```
### Multiple Constraints
```typescript
function compare(
a: T,
b: T
): number {
return a.name.localeCompare(b.name) || a.age - b.age;
}
interface HasName {
name: string;
}
interface HasAge {
age: number;
}
```
## Generic Type Aliases
```typescript
type Result = {
success: boolean;
data?: T;
error?: string;
};
function fetchUser(): Result {
return { success: true, data: user };
}
```
## Default Type Parameters
```typescript
interface Response {
data: T;
status: number;
}
let r1: Response = { data: "OK", status: 200 };
let r2: Response = { data: 42, status: 200 };
```
## Generic Utility Types
### Partial
All properties optional:
```typescript
interface User {
name: string;
age: number;
}
function updateUser(id: number, updates: Partial): User {
// Merge updates with existing user
return { ...existingUser, ...updates };
}
```
### Required
All properties required:
```typescript
interface Config {
host?: string;
port?: number;
}
let partial: Partial = { host: "localhost" };
let full: Required = { host: "localhost", port: 8080 };
```
### Pick
Select properties:
```typescript
interface User {
id: number;
name: string;
email: string;
age: number;
}
type UserPreview = Pick;
// { id: number; name: string }
```
### Omit
Exclude properties:
```typescript
type UserWithoutEmail = Omit;
// { id: number; name: string; age: number }
```
### Record
Create object type:
```typescript
type Role = "admin" | "user" | "guest";
type Permissions = Record;
let perms: Permissions = {
admin: true,
user: false,
guest: true
};
```
### Exclude
Exclude from union:
```typescript
type T = "a" | "b" | "c";
type U = Exclude; // "b" | "c"
```
### Extract
Extract from union:
```typescript
type T = "a" | "b" | "c";
type U = Extract; // "a" | "b"
```
### NonNullable
Remove null and undefined:
```typescript
type T = string | null | undefined;
type U = NonNullable; // string
```
### ReturnType
Get function return type:
```typescript
function createUser() {
return { name: "Alice", age: 30 };
}
type User = ReturnType;
// { name: string; age: number }
```
### Parameters
Get function parameters:
```typescript
function greet(name: string, age: number): string {
return `Hello, ${name}, age ${age}`;
}
type GreetParams = Parameters;
// [name: string, age: number]
```
## Generic with new()
```typescript
function create(constructor: new () => T): T {
return new constructor();
}
class User {
name: string = "Default";
}
let user = create(User);
```
## Generic in Conditional Types
```typescript
type IsArray = T extends any[] ? true : false;
type A = IsArray; // true
type B = IsArray; // false
```
## Summary
- Generics preserve type information
- `` is the most common type parameter name
- Use `extends` to constrain generic types
- Generic functions: `function foo(x: T): T`
- Generic interfaces: `interface Container`
- Generic classes: `class Box`
- Built-in utility types: Partial, Pick, Omit, Record
- Use multiple type parameters: ``
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →