Generics
Types with parameters, constraints with extends, keyof and T[K].
Updated
What generics are
Generics are types with parameters. Just as a function takes values as parameters, a generic takes types as parameters.
The problem they solve:
function first(arr: any[]): any { return arr[0] }
const n = first([1, 2]) // any — you lost the information that it's a number
function first<T>(arr: T[]): T | undefined { return arr[0] }
const m = first([1, 2]) // number ✓
const s = first(['a']) // string ✓T is a type parameter — a blank filled in automatically by TS from the arguments. There's still just one function, but the result's type follows the input's type.
You already use them
const ids: Array<number> = [] // = number[]
const users = new Map<string, User>()
const [count, setCount] = useState<number>(0)
const res: Promise<User> = getUser()Constraints with extends
Sometimes T can't be just anything — it has to have something specific:
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b
}
longest('abc', 'de') // ✓ a string has length
longest([1, 2], [3]) // ✓ an array has length
longest(10, 20) // ✗ a number has no lengthkeyof and access by key
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map(item => item[key])
}
pluck(users, 'email') // string[]
pluck(users, 'emial') // ✗ typo caughtkeyof T = the union of T's keys; T[K] = the type of the value at key K.
Generic types
It's not only functions that can be generic:
type ApiResponse<T> =
| { ok: true; data: T }
| { ok: false; error: string }
type Paginated<T> = { items: T[]; page: number; total: number }
const res: ApiResponse<Paginated<Product>> = await api('/products')When to use generics
| Situation | Generic? |
|---|---|
| a function that works the same with any type (first, groupBy, pluck) | yes |
| a "container" type (an API response, pagination, form state) | yes |
a reusable React component for any item type (List<T>, Select<T>) |
yes |
a function that only works with User |
no — write User |
A sign you're overdoing it: T appears only once in the signature. Then there's no need for a generic.
Summary
- A generic = a type with a parameter; it keeps the link between the input type and the output type.
extendsenforces whatTmust have;keyof T+T[K]for access by key.- Use them for utilities and containers, not everywhere.