Typescript Generics
Table of Content
- What is Generics?
- Why Generics?
- How to use Generics?
- Utility Types (not included)
What is Generics
- a kind of tool that enables you to create reusable code components that allow a number of types instead of a single type.
- able to retrieve the complete type information
- can apply on classes, interfaces, and functions.
Why Generics?
- to solve problem of type 'any' which is lack of complete information
- to enable types, classes or interfaces to act as parameters.
- to reuse the same code for different types of input since the type itself is available as a parameter.
Example
function lastNumber(arr: number[]): number {
return arr[arr.length - 1];
};
const last1 = lastNumber([1, 2, 3]); // Output: 3
function lastString(arr: string[]): string {
return arr[arr.length - 1];
};
const last2 = lastString(["a", "b", "c"]); // Output: c
-
Repeat same function with different type
function lastBoolean(arr: boolean[]): boolean {
return arr[arr.length - 1];
};
const last3 = lastBoolean([false, false, true]); // Output: true
function last(arr: any[]): any {
return arr[arr.length - 1];
};
const last1 = last([1, 2, 3]); // Output: 3
const last2 = last(["a", "b", "c"]); // Output: c
-
Alternative way. we canuUse type 'any'
-
Not recommended as it lose the type information
// typescript does not show error
console.log(last1.length);
console.log(last2.length);
The problem can be solved by using
TypeScript Generics
function last<T>(arr: T[]): T {
return arr[arr.length - 1];
};
const last1 = last([1, 2, 3]); // Output: 3
const last2 = last(["a", "b", "c"]); // Output: c
const last3 = last<boolean>([false, false, true]); // Output: true
console.log("last1", last1.length); // not valid
console.log("last2", last2.length); // valid
console.log("last3", last3.length); // not valid
How to write Generics Types?
- let's use T to make our function become generics
const last = <T>(arr: T[]): T => {
return arr[arr.length - 1];
};
- generics in arrow function
class ArrayClass<T> {
private _list: T[];
constructor() {
this._list = [];
}
add(something: T) {
this._list.push(something);
}
get(index: number): T {
return this._list[index]
}
}
Generics in class
const array1 = new ArrayClass<string>();
array1.add("abc");
const value1 = array1.get(0); // Output: abc
const array2 = new ArrayClass<number>();
array2.add(123);
const value2 = array2.get(0); // Output: 123
class ArrayClass {
private _list: any[];
constructor() {
this._list = [];
}
add(something: any) {
this._list.push(something);
}
get(index: number): any {
return this._list[index]
}
}
const array1 = new ArrayClass();
array1.add("abc");
const value1 = array1.get(0); // Output: abc
const array2 = new ArrayClass();
array2.add(123);
const value2 = array2.get(0); // Output: 123
- type 'any'
- generics type
function makeArr<T, K>(value1: T, value2: K) {
return [value1, value2];
}
// const v: number[]
const v = makeArr(5, 6);
// const v2: string[]
const v2 = makeArr("a", "b");
// const v3: (string | number)[]
const v3 = makeArr("a", 5);
More than 1 Generics Types
function addID(obj: object) {
const id = Math.random()
return { ...obj, id };
}
const obj1 = addID({ name: "kfc", count: 666 });
// not valid
console.log(obj1.name);
Generics for object
function addID<T>(obj: T) {
const id = Math.random()
return { ...obj, id };
}
const obj1 = addID({ name: "kfc", count: 666 });
console.log(obj1.name);
- Generics capture all property information from input obj
const obj2 = addID("kfc");
- Valid input for all type, not only object
Generics with extends
function addID<T extends object>(obj: T) {
const id = Math.random()
return { ...obj, id };
}
// not valid
const obj2 = addID("kfc");
- more specific extends
function getName<T extends { name: string }>(obj: T) {
return obj.name;
}
// valid
const name1 = getName({ name: "kfc", count: 666 });
const name2 = getName({ name: "john", age: 18 });
// not valid
const name3 = getName({ address: "Petaling jaya" });
const name4 = getName({ name: 123 });
Constraint of Generics
function getLength<T>(arg: T) {
return arg.length;
}
- Typescript will throw an exception
interface LengthArgs {
length: number;
}
function getLength<T extends LengthArgs>(args:T) : number {
return args.length;
}
- Example to avoid exception
const l1 = getLength("kfc"); // valid
const l2 = getLength({ name: "ruler", length: 100 }); // valid
const l3 = getLength(28); // not valid
Generics in interface
interface SpatialData<T> {
lat: number;
lon: number;
data: T
}
type stringSpatialData = SpatialData<string>;
type numberSpatialData = SpatialData<number>;
interface POI {
name: string;
address: string;
poiCode: string;
}
type POISpatialData = SpatialData<POI>;
function getName<T extends POISpatialData>(obj: T) {
return obj.data.name
}
Reference
Typescript Generics
By shirlin1028
Typescript Generics
- 285