Generics
Generics are necessary when we want to describe a relationship between two or more types (eg., a function argument and return type).
TS Docs
Functions with generic types
Consider
function identity(arg: any): any {
return arg;
}
let output = identity("myString"); // type of output will be 'any'
let output2 = identity(100); // type of output2 will be 'any'
Functions with generic types : types get bind at later stages (So types are parameterized)
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>("myString"); // type of output will be 'string'
let output2 = identity<number>(100); // type of output2 will be 'number'
Note: T is just a variable, it can be any name
In simple cases, type inference can work, compiler can read parameter value & set it's type to T.
function identity<T>(arg: T): T {
return arg;
}
let output = identity("myString"); // no explicit <string>
let output2 = identity(100);
Note: multiple generic types params can be passed
Interfaces with generic types
consider
interface Record{
id:any;
}
let a:Record = {
id:2
}
a.id //is any
interface Record<T>{
id:T;
}
let a:Record<number> = {
id:2
}
a.id //is number
Taking default generic type
interface Record<T = any>{
id:T;
}
let me:Record = { //no type passed
id:2
}
Describing function signatures
Describing function signatures with generic types
Earlier
interface myEventHandler {
(eventObj: any, message: string): string;
}
const evtButtonClick: myEventHandler = (_contact, _message) => {
};
/*now no need to specify types for function params as
/they are inherited from function signatures type*/
interface myEventHandler<T> {
(eventObj: any, message: T): T;
}
const evtButtonClick: myEventHandler<string> = (_contact, _message) => {
};
/*now no need to specify types for function params
as they are inherited from function signatures type*/
Classes with generic types
class GenericNumber < T, U > {
name: U | null;
constructor(myName: U) {
this.name = myName;
}
doSomething(x: T, y: T) {
};
}
let myGenericNumber = new GenericNumber <number,string>('hi');
myGenericNumber.doSomething(2, 3);
Generic Constraints
Generic Constraints while using generic types
Sample Issue
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
Context fix if input param is array
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length);
return arg;
}
Extending generic types
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;
}
loggingIdentity(3);
// Error, number doesn't have a .length property
loggingIdentity({length: 10, value: 3});
loggingIdentity([1,2,3]]);
Others
Type parameters are associated with scopes, just like function arguments
function startTuple<T>(a: T) {
return function finishTuple<U>(b: U) {
return [a, b] as [T, U];
};
}
const myTuple = startTuple(["first"])(42);