Generics
A generic is a type parameter: a function, type or class is written once
and works with many types, while the compiler still knows how the input and
output types are related. This is the difference from any, which switches the
checks off. In most calls the type argument is inferred.
Applies to: TypeScript 7.0.
Details
- Constraint:
<T extends Shape>limits which types are allowed and lets the code use the properties ofShape. keyofconstraint:<T, K extends keyof T>ties one parameter to the keys of another.- Default:
<T = unknown>is used when the argument is neither passed nor inferred. consttype parameter:<const T extends readonly string[]>infers literal types from the argument, as if the caller wroteas const(TypeScript 5.0).- Generic types: interfaces, type aliases and classes take parameters too:
ApiResponse<User>,Map<string, number>,Promise<void>. - Utility types such as
Partial<T>andPick<T, K>are generics. - In React:
useState<User | null>(null), generic components and custom hooks; see Typing React. - In a
.tsxfile a generic arrow function is written as<T,>(value: T) => …, otherwise<T>is parsed as a JSX tag.
Example
function first<T>(items: T[]): T | undefined {
return items[0];
}
const a = first([1, 2, 3]); // number | undefined, T is inferred
const b = first<string>([]); // string | undefined, T is passed explicitly
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: 'Ann' };
const userName = getProp(user, 'name'); // string
getProp(user, 'age');
// Error: Argument of type '"age"' is not assignable to parameter of type '"id" | "name"'.
interface ApiResponse<T = unknown> {
data: T;
error?: string;
}
const response: ApiResponse<{ id: number }> = { data: { id: 1 } };
A generic React component:
type ListProps<T> = {
items: T[];
getKey: (item: T) => string;
renderItem: (item: T) => React.ReactNode;
};
function List<T>({ items, getKey, renderItem }: ListProps<T>) {
return (
<ul>
{items.map((item) => (
<li key={getKey(item)}>{renderItem(item)}</li>
))}
</ul>
);
}
// T is inferred as { id: string; title: string }
const list = (
<List
items={[{ id: '1', title: 'First' }]}
getKey={(item) => item.id}
renderItem={(item) => item.title}
/>
);
Follow-up Questions
- How is a generic different from
any?anyloses the type; a generic carries it from the arguments to the result. - What does
extendsmean in<T extends U>? A constraint:Tmust be assignable toU. It is not class inheritance. - When is a generic not needed? When the type parameter is used only once. Then
it relates nothing and can be replaced with a concrete type or
unknown.
Common Mistakes
- Passing type arguments everywhere instead of relying on inference.
- Using
as Tinside a generic function to silence the compiler: the function then promises a type it does not check. - Adding a type parameter that appears only in the return type, so the caller
can claim any type (
get<T>(): T). This is a type assertion in disguise.