Skip to content
Created: October 7, 2020

Generics ​

  • Generics refers to a characteristic that does not specify a specific type in advance when defining a function, interface or class, but then specifies the type when using it.
  • The scope of generic T is limited to internal use of functions

1. Generic function ​

  • First, let's implement a function createArray, which can create an array of a specified length and fill each item with a default value.
ts
function createArray(length: number, value: any): Array<any> {
  let result: any = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}
let result = createArray(3, "x");
console.log(result);

Generics are used

ts
function createArray<T>(length: number, value: T): Array<T> {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}
let result = createArray2<string>(3, "x");
console.log(result);

2. Array-like ​

  • Array-like Object is not an array type, such as arguments
ts
function sum() {
  let args: IArguments = arguments;
  for (let i = 0; i < args.length; i++) {
    console.log(args[i]);
  }
}
sum(1, 2, 3);

let root = document.getElementById("root");
let children: HTMLCollection = (root as HTMLElement).children;
children.length;
let nodeList: NodeList = (root as HTMLElement).childNodes;
nodeList.length;

3. Generic class ​

3.1 Generic classes ​

ts
class MyArray<T> {
  private list: T[] = [];
  add(value: T) {
    this.list.push(value);
  }
  getMax(): T {
    let result = this.list[0];
    for (let i = 0; i < this.list.length; i++) {
      if (this.list[i] > result) {
        result = this.list[i];
      }
    }
    return result;
  }
}
let arr = new MyArray();
arr.add(1);
arr.add(2);
arr.add(3);
let ret = arr.getMax();
console.log(ret);

3.2 Generics and new ​

ts
function factory<T>(type: { new (): T }): T {
  return new type(); // This expression is not constructable.
}

5. Generic interface ​

  • Generic interfaces can be used to constrain functions
ts
interface Calculate {
  <T>(a: T, b: T): T;
}
let add: Calculate = function <T>(a: T, b: T) {
  return a;
};
add<number>(1, 2);

6. multiple type parameters ​

  • Generics can have multiple
ts
function swap<A, B>(tuple: [A, B]): [B, A] {
  return [tuple[1], tuple[0]];
}
let swapped = swap<string, number>(["a", 1]);
console.log(swapped);
console.log(swapped[0].toFixed(2));
console.log(swapped[1].length);

7. Default generic type ​

ts
function createArray3<T = number>(length: number, value: T): Array<T> {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}
let result2 = createArray3(3, "x");
console.log(result2);

8. Generic constraints ​

  • When using generics in a function, since the type of the generic is not known in advance, the properties or methods of the corresponding type cannot be accessed at will.
ts
function logger<T>(val: T) {
  console.log(val.length); //直接访问会报错
}
//可以让泛型继承一个接口
interface LengthWise {
  length: number;
}
//可以让泛型继承一个接口
function logger2<T extends LengthWise>(val: T) {
  console.log(val.length);
}
logger2("hello ");
logger2(1);

9. Generic interface ​

  • Generics can also be specified when defining an interface
ts
interface Cart<T> {
  list: T[];
}
let cart: Cart<{ name: string; price: number }> = {
  list: [{ name: "hello ", price: 10 }],
};
console.log(cart.list[0].name, cart.list[0].price);

10. compose ​

compose

ts
type Func<T extends any[], R> = (...a: T) => R;

/**
 * Composes single-argument functions from right to left. The rightmost
 * function can take multiple arguments as it provides the signature for the
 * resulting composite function.
 *
 * @param funcs The functions to compose.
 * @returns A function obtained by composing the argument functions from right
 *   to left. For example, `compose(f, g, h)` is identical to doing
 *   `(...args) => f(g(h(...args)))`.
 */
export default function compose(): <R>(a: R) => R;

export default function compose<F extends Function>(f: F): F;

/* two functions */
export default function compose<A, T extends any[], R>(
  f1: (a: A) => R,
  f2: Func<T, A>,
): Func<T, R>;

/* three functions */
export default function compose<A, B, T extends any[], R>(
  f1: (b: B) => R,
  f2: (a: A) => B,
  f3: Func<T, A>,
): Func<T, R>;

/* four functions */
export default function compose<A, B, C, T extends any[], R>(
  f1: (c: C) => R,
  f2: (b: B) => C,
  f3: (a: A) => B,
  f4: Func<T, A>,
): Func<T, R>;

/* rest */
export default function compose<R>(
  f1: (a: any) => R,
  ...funcs: Function[]
): (...args: any[]) => R;

export default function compose<R>(...funcs: Function[]): (...args: any[]) => R;

export default function compose(...funcs: Function[]) {
  if (funcs.length === 0) {
    // infer the argument type so it is usable in inference down the line
    return <T>(arg: T) => arg;
  }

  if (funcs.length === 1) {
    return funcs[0];
  }

  return funcs.reduce(
    (a, b) =>
      (...args: any) =>
        a(b(...args)),
  );
}
ts
import compose from ".";
/* zero functions */
console.log(compose()<string>("hello "));
/* one functions */
interface F {
  (a: string): string;
}
let f: F = (a: string): string => a + "f";
console.log(compose<F>(f)("hello "));
/* two functions */
type A = string;
type R = string;
type T = string[];

let f1 = (a: A): R => a + "f1";
let f2 = (...a: T): A => a + "f2";
console.log(compose<A, T, R>(f1, f2)("hello "));

11. Generic type alias ​

  • Generic type aliases can express more complex types
ts
type Cart<T> = { list: T[] } | T[];
let c1: Cart<string> = { list: ["1"] };
let c2: Cart<number> = [1];

12. Generic interface vs generic type alias ​

  • The interface creates a new name that can be called anywhere else.Type aliases do not create new names. For example, error messages will not use aliases.
  • Type aliases cannot be extended and implements. In this case, we should try to use interfaces instead of type aliases.
  • Type aliases are more appropriate when we need to use union types or tuple types