Generics in TypeScript provide a way to create reusable components that work with a variety of data types while preserving type safety. By using generics, you can create functions, classes, interfaces, and other structures that can operate on different types without sacrificing the benefits of strong typing. This guide will explore the various aspects of TypeScript generics, including generic functions, types, interfaces, and more.
Basic concept of TypeScript generics
Generics allow you to define a placeholder for a type that can be specified later. This is done using angle bracket syntax (<>
) with a type parameter.
function identity<T>(arg: T): T {return arg}let result = identity<string>('Hello, TypeScript')console.log(result) // Output: Hello, TypeScript
In this example, T
is a type parameter that represents an argument to the function. When calling identity
, we specify that T
should be a string
.
TypeScript generic function
A generic function is a function that can work with any data type. You define a generic function by adding a type parameter in angle brackets before the function's parameters.
function loggingIdentity<T>(arg: T[]): T[] {console.log(arg.length) // Array has a .length property, so no errorreturn arg}let result = loggingIdentity<number>([1, 2, 3])console.log(result) // Output: [1, 2, 3]
Here, loggingIdentity
is a generic function that takes an array of type T
and logs its length. The function returns the same array.
TypeScript generic type
Generic types are types that take one or more type parameters. This allows you to define a type that can be reused with different data types.
interface GenericIdentityFn<T> {(arg: T): T}function identity<T>(arg: T): T {return arg}let myIdentity: GenericIdentityFn<number> = identityconsole.log(myIdentity(10)) // Output: 10
In this example, GenericIdentityFn
is a generic interface that represents a function accepting and returning a value of type T
. We then create a variable myIdentity
of this type, specifying number
as the type parameter.
TypeScript generic arrow function
Arrow functions in TypeScript can also be generic. You define a generic arrow function by adding a type parameter before the function's parameters.
const genericArrowFunction = <T>(arg: T): T => {return arg}let result = genericArrowFunction<string>('Hello, arrow functions')console.log(result) // Output: Hello, arrow functions
In this example, genericArrowFunction
is a generic arrow function that takes a value of type T
and returns the same value.
TypeScript generic object
You can define generic objects by using generic interfaces or type aliases.
interface Box<T> {contents: T}let stringBox: Box<string> = { contents: 'Hello, World' }console.log(stringBox.contents) // Output: Hello, Worldlet numberBox: Box<number> = { contents: 100 }console.log(numberBox.contents) // Output: 100
In this example, Box
is a generic interface that represents an object with a contents
property of type T
. We then create instances of Box
with different types.
TypeScript generic interface
Generic interfaces allow you to define a contract that can be applied to various types.
interface KeyValuePair<K, V> {key: Kvalue: V}let kvp: KeyValuePair<string, number> = { key: 'Age', value: 30 }console.log(kvp.key) // Output: Ageconsole.log(kvp.value) // Output: 30
Here, KeyValuePair
is a generic interface with two type parameters, K
and V
. We create an instance of KeyValuePair
with string
as the key type and number
as the value type.
TypeScript optional generic
TypeScript also supports optional generics, where you can provide default types for type parameters.
function createArray<T = string>(length: number, value: T): T[] {return Array(length).fill(value)}let stringArray = createArray(3, 'Hello')console.log(stringArray) // Output: ["Hello", "Hello", "Hello"]let numberArray = createArray<number>(3, 42)console.log(numberArray) // Output: [42, 42, 42]
In this example, the createArray
function has a generic type parameter T
with a default type of string
. If no type is specified, T
defaults to string
.
Advanced examples
Constraining generics
Sometimes, you need to constrain the types that can be used as generic parameters. This is done using the extends
keyword.
function getLength<T extends { length: number }>(arg: T): number {return arg.length}console.log(getLength('Hello')) // Output: 5console.log(getLength([1, 2, 3, 4])) // Output: 4
In this example, T
is constrained to types that have a length
property. This allows the function to access the length
property without causing a type error.
Using generics in classes
Generics can also be used in classes to create flexible and reusable components.
class DataStorage<T> {private data: T[] = []addItem(item: T) {this.data.push(item)}removeItem(item: T) {this.data = this.data.filter((i) => i !== item)}getItems() {return [...this.data]}}let textStorage = new DataStorage<string>()textStorage.addItem('Hello')textStorage.addItem('World')textStorage.removeItem('Hello')console.log(textStorage.getItems()) // Output: ["World"]let numberStorage = new DataStorage<number>()numberStorage.addItem(10)numberStorage.addItem(20)numberStorage.removeItem(10)console.log(numberStorage.getItems()) // Output: [20]
In this example, DataStorage
is a generic class that can store items of any type. The type is specified when creating an instance of the class.
For further reading on generic types in TypeScript, see the official documentation.