Generics are a powerful feature in TypeScript that allow you to write flexible and reusable code. They enable you to create components or functions that can operate on a variety of types while still maintaining type safety. This tutorial will explore how to use generics with functions, providing detailed explanations, real-world examples, and best practices.
Generics in TypeScript provide a way to define types that are parameterized over other types. They allow you to write code that can work with different data types without losing type safety. This is particularly useful when you want to create functions or classes that can handle multiple types of inputs.
A generic function is defined using angle brackets (<>) to specify the type parameters. Here's a simple example:
function identity<T>(arg: T): T {
return arg;
}
In this example, T is a type parameter that represents the type of the argument passed to the function and the type of the value returned by the function.
TypeScript can often infer the types of generic parameters based on the arguments you pass to a function. For instance:
let output = identity<string>("myString");
// TypeScript infers T as string
You can also let TypeScript infer the type automatically:
let output = identity("myString"); // TypeScript infers T as string
Functions can have multiple type parameters. This is useful when you need to work with more than one type in a function.
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
let swapped = swap([7, "hello"]); // TypeScript infers T as number and U as string
Sometimes you might want to restrict the types that can be used with a generic function. You can do this using type constraints.
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
In this example, the T type parameter is constrained to types that have a length property.
TypeScript also supports default parameters in generic functions. This can be useful when you want to provide a default type for a type parameter.
function createArray<T = string>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, "hello"); // ["hello", "hello", "hello"]
You can also use generics with function types. This is useful when you want to define a type that represents a function.
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); // returns 1
getProperty(x, "m"); // error: Argument of type '"m"' is not assignable to parameter of type 'keyof typeof x'.
In this example, K is constrained to be a key of the object T.
Let's consider a real-world example where generics can be useful. Suppose you have a function that fetches data from an API and returns it in a specific format. You want this function to work with different types of data without losing type safety.
interface ApiResponse<T> {
status: number;
data: T;
}
async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data: T = await response.json();
return { status: response.status, data };
}
interface User {
id: number;
name: string;
}
async function getUser(userId: number): Promise<ApiResponse<User>> {
const url = `https://api.example.com/users/${userId}`;
return fetchData<User>(url);
}
In this example, the fetchData function is generic and can be used to fetch different types of data. The getUser function uses fetchData to fetch user data from an API.
Generics are a powerful feature in TypeScript that allow you to write flexible and reusable code. By understanding how to use generics with functions, you can create more robust and maintainable applications. This tutorial has covered the basics of generic functions, including type inference, multiple type parameters, constraints, default parameters, function types, best practices, and real-world examples.