SoFunction
Updated on 2025-04-06

Detailed explanation of the use of generics in TypeScript

1. Generic programming is a programming style or programming paradigm

2. Case: The type of the passed parameter is the same as the type returned.

function identify<T>(arg: T): T {// The current T has no constraints. It can be of any type    return arg;
}
 
const foo = identify('foo'); // The type of foo is 'foo'const bar = identify('true'); // barThe type istrue

3. Form type parameters

1. The default type of the form type parameter T is boolean type

<T = boolean>

2. Required type parameters, optional type parameters

(1) Required type parameters: The form type parameters are not given to the default type, for example: <T>

(2) Optional type parameters: Formal type parameters are given to the default type, for example: <T = boolean>

(3) In the form type parameter list, the required type parameters are not allowed to appear after the optional type parameters

<T = boolean, U> // Error
<T, U = boolean> // Correct

4. Generic constraints

A constraint is allowed to be defined on a formal type parameter, which can define the maximum range of the actual type of the type parameter. We call the constraints of type parameters generic constraints

interface point {
    x: number,
    y: string
}
 
function getPoint&lt;T extends point&gt;(args: T): T {
    return args
}
 
(getPoint({x: 123, y: 456})); // mistake(getPoint({x: 123, y: '456'})); // correct(getPoint({x: 123, y: '456', z: 678})); // correct//The first two parameters must have and the type must be correct  Otherwise, error

Generic constraints and default types can be defined at the same time

<T extends number = 0 | 1>

Generic Constraint ==> Type Parameters

<T, U extends T>
<T extends U, U>

Formal type parameters do not allow themselves to be directly or indirectly as constraint types

&lt;T extends T&gt; // mistake&lt;T extends U, U extends T&gt; // mistake

The type parameter T does not declare a generic constraint, so the base constraint of the type parameter T is the empty object type literal "{}". Except for the undefined type and null type, any other type can be assigned to the null object type literal.

&lt;T&gt; // Type parametersTThe cardinality of“{}”type

V. Generic functions

1. Introduction: If a function's function signature (formal parameters) contains type parameters, then it is a generic function.

2. The two parameters of the f1 function have the same type. The return value type of the function is an array, and the array element type == parameter type.

function f1<T>(x: T, y: T): T[] {
    return [x, y]
}
 
const a = f1(123, 456)
const b = f1('123', '456')

3. The two parameters of the f2 function have different types, and the return value type is the object type. The type of x attribute in the return value object type is the same as the parameter x type, and the type of y attribute is the same as the parameter y type.

function f2<T, U>(x: T,  y: U): { x: T, y: U} {
    return { x, y }
}
 
const a = f2('123', 456)
const b = f2(123, '456')

4. The f3 function accepts two parameters, and parameter a is an array of any type; parameter f is a function whose parameter type is the same as parameter a and returns any type. The return value type of the f3 function is an array of parameter f return value type.

(I was also confused when pasting this code)

function f3&lt;T, U&gt;(a: T[], f: (x: T) =&gt; U): U[] {
    return (f);
}
 
// f3<number, boolean> Constraint T is number type U is boolean typeconst a: boolean[] = f3&lt;number, boolean&gt;([1, 2, 3], n =&gt; !! n)

6. Generic function type inference

function f0&lt;T&gt;(x: T): T {
    return x
}
 
const a = f0(123) // Inferred type is 123const b = f0('123') // Inferred type is '123'

In this example, the compiler infers not the number and string types, but the string literal types 123 and "123".Because of the TS principle, literals are always treated as literal types, and literal types are only relaxed to some basic type when necessary, such as string types. In this example, the string literal type "123" is a more accurate type than the string type.

If a type parameter appears only once in the function signature, it means that it is not associated with other values. You do not need to use type parameters, just declare the actual type directly.

Almost any function can be declared as a generic function. If the type parameter of a generic function does not represent some relationship between parameters or between parameters and return values, then using a generic function may be an anti-pattern.

// There is no need to use genericsfunction f&lt;T&gt;(x: T): void {
    (x)
}
 
// Just limit it directlyfunction f(x: number): void {
    (x)
}

Supplementary: Application scenarios

Through the preliminary understanding above, it is later explained that when writing typescript, when defining functions, interfaces or classes, you do not define specific types in advance. When using them, when specifying a feature of the type, you can use generics in this case

Flexible use of generics to define types is the only way to master typescript

<generic variable name> (parameter 1: generic variable, parameter 2: generic variable, ...parameter n: generic variable) => generic variable

 /*-----------------------------------*/
  function join&lt;T, P&gt;(first: T, second: P): T {
    return first;
  }
  //const twoParms = join<number, string>(1, 'I'm string');  const twoParms = join(1, 'I'm string');

  /*---------------------------------------------------------/
  function map&lt;T&gt;(params: Array&lt;T&gt;) {
    return params;
  }
  //const sanleType = map&lt;string&gt;(['123']);
  const sanleType = map(['123']);

  /* ------------------------------------------------------------------/
  const identity = &lt;T,&gt;(arg: T): T =&gt; {
    return arg;
  };
  const identity2: &lt;T&gt;(arg: T) =&gt; T = (arg) =&gt; {
    return arg;
  };

Generic interface

 /* ----------------------------------------------------------------/
 interface ColumnProps&lt;T&gt; {
  key: number | string;
  title: string;
  dataIndex: keyof T; // The constraint dataIndex value must be a property in the reference generic T}
interface ITableItem {
  key: number | string;
  name: string;
  address: string;
  age: number;
}
const columns: Array&lt;ColumnProps&lt;ITableItem&gt;&gt; = [
    {
      title: 'Name',
      dataIndex: 'name',
      key: 'name',
    },
  ];

Generic Classes

 /*--------------------------------------------------------------------------------/
  class Person&lt;T&gt; {
    love: T;
    say: (arg: T) =&gt; T;
  }
  let myFn: IGeneric&lt;number&gt; = fn;
  myFn(13); //13
 
  let me = new Person&lt;string&gt;();
   = 'TS';
  //  = 520; // ERROR
   = function(love: string){
    return `my love is ${love}`;
  }

Generic constraints

Generics can be implemented through extends an interface to implement generic constraints, and the writing method is as follows:

<generic variables extends interface>

&lt;T, K extends keyof T&gt;
//K is a known property on the incoming T,
interface IArray {
  length: number
}
function logIndex&lt;T extends IArray&gt;(arg: T): void {
  for (let i = 0; i &lt; ; ++i) {
    (i)
  }
}
let arr = [1, 2, 3]
// logIndex<number>(arr) // ErrorlogIndex&lt;number[]&gt;(arr) // allowlogIndex(arr) // Automatic type derivation, allow

One of the generic application scenarios

/*--------------------------------------------------------------------------------/
interface ColumnProps&lt;T&gt; {
  key: number | string;
  title: string;
  dataIndex: keyof T; // The constraint dataIndex value must be a property in the reference generic T}
interface ITableItem {
  key: number | string;
  name: string;
  address: string;
  age: number;
}
interface TableProps {
  dataSource: ITableItem[];
  columns: Array&lt;ColumnProps&lt;ITableItem&gt;&gt;;
}
const MyTable = (props: TableProps) =&gt; {
  const { dataSource, columns } = props;
  return &lt;Table dataSource={dataSource} columns={columns} /&gt;;
};
const ApplcationMod = () =&gt; {
  const dataSource = [
    {
      key: '1',
      name: 'Take Kaneshiro',
      age: 32,
      address: 'No. 1 Hudi Park, Xihu District',
    },
    {
      key: '2',
      name: 'Danzu Wu',
      age: 42,
      address: 'No. 1 Hudi Park, Xihu District',
    },
  ];

  const columns: Array&lt;ColumnProps&lt;ITableItem&gt;&gt; = [
    {
      title: 'Name',
      dataIndex: 'name',
      key: 'name',
    },
    {
      title: 'age',
      dataIndex: 'age',
      key: 'age',
    },
    {
      title: 'address',
      dataIndex: 'address',
      key: 'address',
    },
  ];

  return (
    &lt;div&gt;
      &lt;h3&gt;Generic application scenarios&lt;/h3&gt;
      &lt;MyTable dataSource={dataSource} columns={columns} /&gt;
    &lt;/div&gt;
  );
};

Summarize

This is the end of this article about the use of generics in TypeScript. For more information about using TypeScript generics, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!