Generics in TypeScript
Learn how to use Generics in TypeScript with this detailed tutorial. Includes basic and advanced examples to help you master Generics.
Introduction
Generics provide a way to create reusable components that work with a variety of types rather than a single one. They allow you to define functions, classes, and interfaces with a placeholder for types that can be specified when they are used.
Basic Examples
Here are some basic examples of using Generics in TypeScript:
Generic Functions
You can define a generic function using a type parameter.
function identity(arg: T): T {
return arg;
}
let output1 = identity("myString"); // output1 is of type string
let output2 = identity(42); // output2 is of type number
Generic Interfaces
You can define a generic interface that can be used with different types.
interface GenericIdentityFn {
(arg: T): T;
}
function identity(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
console.log(myIdentity(10)); // 10
Generic Classes
You can define a generic class that can be used with different types.
class GenericNumber {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) {
return x + y;
};
console.log(myGenericNumber.add(5, 10)); // 15
Generic Constraints
You can constrain the types that can be used with a generic type.
interface Lengthwise {
length: number;
}
function loggingIdentity(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity({ length: 10, value: 3 });
Advanced Examples
Here are some advanced examples of using Generics in TypeScript:
Using Class Types in Generics
You can use class types in generics to create more flexible and reusable components.
function create(c: { new(): T }): T {
return new c();
}
class BeeKeeper {
hasMask: boolean = true;
}
class ZooKeeper {
nametag: string = "Mikle";
}
class Animal {
numLegs: number = 4;
}
class Bee extends Animal {
keeper: BeeKeeper = new BeeKeeper();
}
class Lion extends Animal {
keeper: ZooKeeper = new ZooKeeper();
}
function createInstance(c: new () => A): A {
return new c();
}
createInstance(Lion).keeper.nametag; // typechecks!
createInstance(Bee).keeper.hasMask; // typechecks!
Generic Constraints with keyof
You can use keyof
to constrain the keys that can be used with a generic type.
function getProperty(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
console.log(getProperty(x, "a")); // 1
console.log(getProperty(x, "m")); // Error: Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.
Using Type Parameters in Generic Constraints
You can use type parameters in generic constraints to create more complex relationships between types.
function getInstance(c: new () => T): T {
return new c();
}
class Animal {
numLegs: number = 4;
}
class Bee extends Animal {
keeper: BeeKeeper = new BeeKeeper();
}
class Lion extends Animal {
keeper: ZooKeeper = new ZooKeeper();
}
createInstance(Lion).keeper.nametag; // typechecks!
createInstance(Bee).keeper.hasMask; // typechecks!
Generic Functions with Multiple Type Parameters
You can define generic functions with multiple type parameters.
function merge(obj1: T, obj2: U): T & U {
let result = {} as T & U;
for (let id in obj1) {
(result as any)[id] = (obj1 as any)[id];
}
for (let id in obj2) {
if (!result.hasOwnProperty(id)) {
(result as any)[id] = (obj2 as any)[id];
}
}
return result;
}
let mergedObj = merge({ name: "John" }, { age: 30 });
console.log(mergedObj.name); // John
console.log(mergedObj.age); // 30
Generic Interfaces with Multiple Type Parameters
You can define generic interfaces with multiple type parameters.
interface Pair {
first: T;
second: U;
}
let pair: Pair = {
first: "hello",
second: 42
};
console.log(pair.first); // hello
console.log(pair.second); // 42
Conclusion
Generics in TypeScript provide a powerful way to create reusable and flexible components. By using generics, you can write code that works with a variety of types, improving the flexibility and reusability of your code. Whether you are working on functions, classes, or interfaces, mastering generics in TypeScript will help you write more robust and maintainable code.