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

  • 303