• 泛型(Generics)是指在定义函数、接口、类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
  • 泛型 T 作用域只限于函数内部使用

泛型函数

为什么需要泛型?

实现一个函数createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值。

  1. function createArray (length:number, value:any):Array<any>{
  2. let result:Array<any> = [];
  3. for (let i=0; i<length; i++){
  4. result[i] = value;
  5. //如果像下面这样子也不会报错,但就不是 想要的那样子,每一项填充value值了。
  6. // 那么,如何约束? 这就需要泛型了。
  7. result[i] = i;
  8. }
  9. return result;
  10. }
  11. console.log(createArray(3, 'x')); // [ 'x', 'x', 'x' ]

使用泛型改造
  1. // 第一个"<T>" 是定义一种 "T" 类型,T 在这里是不确定的类型。
  2. // 后边的T,就是表示:用前边定义的 T类型 去约束相应变量。
  3. function createArray<T>(count:number, value:T):T[]{
  4. let result:T[] = [];
  5. for (let i=0; i<count; i++){
  6. result.push(value);
  7. // result[i] = i; //报错:数组的值类型必须是string,不能将number类型的值分配给数组
  8. }
  9. return result;
  10. }
  11. // 调用的时候,才会确定 T 的具体类型
  12. // 这里传入了 string类型,所以上面的 T类型 就是 string类型;
  13. // 这里如果传入了 number类型,上面的 T类型 就是 number类型;
  14. let p1 = createArray<number>(5, 1);
  15. let p2 = createArray<string>(5, 'a');
  16. console.log(p1); //[1, 1, 1, 1, 1]
  17. console.log(p2); //["a", "a", "a", "a", "a"]
  1. interface ICreateArray {
  2. <T>(count:number, value:T):T[],
  3. }
  4. // type ICreateArray = <T>(count:number, value:T) => T[];
  5. const createArray:ICreateArray = <T>(count:number, value:T):T[] => {
  6. let result:T[] = [];
  7. for (let i=0; i<count; i++){
  8. result.push(value);
  9. }
  10. return result;
  11. }
  12. console.log(createArray(5, 1)); //[1, 1, 1, 1, 1]
  13. console.log(createArray(3, 'a')); //["a", "a", "a"]

类数组

类数组(Array-like Object)不是数组类型,比如 arguments

  1. function sum(){
  2. let args:IArguments = arguments;
  3. for (let i=0; i<args.length; i++){
  4. console.log(args[i]);
  5. }
  6. // for (let k of args){
  7. // console.log(args[k]);
  8. // }
  9. }
  10. sum(1,2,3); //现在已经不支持了。报错:应有0个参数,实际有3个
  11. let root = document.getElementById('root');
  12. let children: HTMLCollection = (root as HTMLElement).children;
  13. console.log(children.length);
  14. let nodeList: NodeList = (root as HTMLElement).childNodes;
  15. console.log(nodeList.length);

泛型类

  1. class MyArray<T>{
  2. public arr: T[] = [];
  3. add(val:T){
  4. this.arr.push(val);
  5. }
  6. getMax():T{
  7. return this.arr.reduce((x,y) => x > y ? x : y);
  8. }
  9. }
  10. let arr = new MyArray<number>(); //泛型传一个 number 类型进去
  11. arr.add(1);
  12. arr.add(2);
  13. arr.add(3);
  14. console.log(arr.getMax());

如果不传泛型的话,会默认为 unknown 类型

image.png

正常传入的类,其类型会被推导成 (typeof Person)
  • Person 默认指类实例的类型;
  • type Person 指类的类型。所以ts中想用类的类型,是无法直接用的,必须通过 {new():Person}来实现。

image.png

参数是类时,尖括号中默认为类实例的类型,而不是类的类型。
  1. // 类的类型
  2. interface IClazz<T>{
  3. new(name:string):T
  4. }
  5. // 函数的参数是一个类,返回值是该类的实例
  6. // function createClass<T>(clazz: {new(name:string):T}):T{
  7. function createClass<T>(clazz: IClazz<T>):T{
  8. return new clazz('jack');
  9. }
  10. class Person {
  11. constructor(public name:string){
  12. this.name = name;
  13. }
  14. eat(){}
  15. }
  16. // 尖括号中的类型 Person 默认指的是 Person 类实例的类型,而不是 Person 类的类型。
  17. let p1 = createClass<Person>(Person);
  18. console.log(p1);

泛型接口

泛型接口可以用从来约束函数。

泛型放在函数前,表示使用函数的时候确定了类型
  1. interface Calculate{
  2. <T>(x:T, y:T):T; //一个函数,参数x,y类型都是T,函数返回值类型也是T
  3. }
  4. let sum:Calculate = function<T>(x:T, y:T):T{
  5. // return x+y; //报错:运算符“+”不能应用于类型“T”和“T”。
  6. return x;
  7. }
  8. console.log(sum<number>(1,2)); //1

泛型放在接口后面,表示在使用接口的时候确定类型
  1. interface Calculate<T>{
  2. (x:T, y:T):T;
  3. }
  4. let sum:Calculate<number> = function(x:number, y:number):number{
  5. return x+y;
  6. }
  7. console.log(sum(1,2)); //3
  1. namespace c{
  2. interface CalCulate<T>{
  3. <U>(x:T, y:T):U;
  4. }
  5. let sum:CalCulate<number> = function<U>(x:number, y:number):U{
  6. return ((x as any) + (y as any)).toString();
  7. }
  8. console.log(sum<string>(1,2)); //"3"
  9. }

多个类型参数

泛型可以有多个

  1. // 元祖交换
  2. function swap<A,B>(tuple:[A,B]):[B,A]{
  3. return [tuple[1], tuple[0]];
  4. }
  5. let s = swap<string,number>(['a',1]);
  6. console.log(s); //[ 1, 'a' ]

默认泛型类型

  1. function createArray3<T=number>(length: number, value: T): Array<T> {
  2. let result: T[] = [];
  3. for (let i = 0; i < length; i++) {
  4. result[i] = value;
  5. }
  6. return result;
  7. }
  8. let result2 = createArray3(3,'x');
  9. console.log(result2); //['x', 'x', 'x']
  1. interface ISchool<T=number>{
  2. name:T
  3. }
  4. type StringSchool = ISchool<string>;
  5. // 不传泛型时,如果有默认类型,就是取默认类型;如果没有,就是 unknown 类型。
  6. type NumberSchool = ISchool; //等同于 type NumberSchool = ISchool<number>;
  7. let s1:StringSchool = {name: '1'};
  8. let s2:NumberSchool = {name: 1};

泛型约束

在函数中使用泛型的时候,由于预先并不知道泛型的类型,所以不能随意访问相应类型的属性和方法。

示例
  1. namespace a{
  2. const sum = <T>(a:T, b:T):T => {
  3. return a + b; //报错:运算符 “+” 不能用于 类型 “T” 和 “T”
  4. // return (a + b) as T; //即使加上断言也是不行的
  5. }
  6. console.log(sum(1, 2));
  7. }
  8. namespace b{
  9. // 为泛型 T 加上约束(T 必须是 number 的子类型)
  10. const sum = <T extends number>(a:T, b:T):T => {
  11. // return a + b; //报错:不能将类型“number”分配给类型“T”
  12. return (a + b) as T; //当然,约束后,还是要加上断言的
  13. }
  14. console.log(sum(1, 2)); //3
  15. }

示例
  1. function logger<T>(val:T){
  2. // console.log(val.length); //直接访问会报错
  3. }
  4. // 可以让泛型继承一个接口
  5. interface LengthWise {length:number}
  6. //约束泛型 T 必须是 LengthWise 的子类型
  7. function logger2<T extends LengthWise>(val:T){
  8. console.log(val.length);
  9. }
  10. logger2('jack'); //4
  11. // logger2(1);
  12. // 报错:number类型的参数没有length属性,所以类型 “number” 的参数不能赋给类型 “LengthWise” 的参数
  13. let obj = {length: 10};
  14. type Obj = typeof obj;
  15. logger2<Obj>(obj); //10

示例3
  1. const getVal = <T extends object, K extends keyof T>(obj:T, key:K) => {
  2. return obj[key];
  3. }
  4. console.log(getVal({x: 1, y: 2}, 'x')); //1
  5. console.log(getVal({x: 1, y: 2}, 'name'));
  6. //报错:类型 “name” 的参数不能赋给类型 “'x' | 'y'” 的参数

示例4
  1. namespace a{
  2. // 判断兼不兼容跟extends继承没有一点关系,只看形状 有没有对应的属性
  3. class GrandFather{}
  4. class Father extends GrandFather{}
  5. class Child extends Father{}
  6. function get<T extends Father>(){}
  7. get<GrandFather>();
  8. get<Father>();
  9. get<Child>();
  10. }
  11. namespace b{
  12. class GrandFather{
  13. grandFather:string = '1';
  14. }
  15. class Father extends GrandFather{
  16. father:string = '2';
  17. }
  18. class Child extends Father{
  19. child:string = '3';
  20. }
  21. //约束,要满足:T的实例能赋值给Father的实例
  22. //也可以认为是 T是Father的子类型
  23. function get<T extends Father>(){}
  24. get<Father>();
  25. get<Child>();
  26. // get<GrandFather>(); //报错了:
  27. // 类型 "GrandFather" 不满足约束
  28. // 类型 "GrandFather" 中缺少属性 "father" ,但类型 "Father" 中需要该属性
  29. //子类的实例可以赋值给父类的实例;但是不能反过来赋值。
  30. let father = new Father();
  31. let child = new Child();
  32. father = child;
  33. // child = father; //报错:类型 "Father" 中缺少属性 "child" ,但类型 "Child" 中需要该属性。
  34. }

compose

  1. //声明一个 Func 类型的函数
  2. type Func<T extends any[], R> = (...a: T) => R
  3. /**
  4. * Composes single-argument functions from right to left. The rightmost
  5. * function can take multiple arguments as it provides the signature for the
  6. * resulting composite function.
  7. *
  8. * @param funcs The functions to compose.
  9. * @returns A function obtained by composing the argument functions from right
  10. * to left. For example, `compose(f, g, h)` is identical to doing
  11. * `(...args) => f(g(h(...args)))`.
  12. */
  13. /* zero functions */
  14. export default function compose(): <R>(a: R) => R
  15. /* one functions */
  16. export default function compose<F extends Function>(f: F): F
  17. /* two functions */
  18. export default function compose<A, T extends any[], R>(
  19. f1: (a: A) => R,
  20. f2: Func<T, A>
  21. ): Func<T, R>
  22. /* three functions */
  23. export default function compose<A, B, T extends any[], R>(
  24. f1: (b: B) => R,
  25. f2: (a: A) => B,
  26. f3: Func<T, A>
  27. ): Func<T, R>
  28. /* four functions */
  29. export default function compose<A, B, C, T extends any[], R>(
  30. f1: (c: C) => R,
  31. f2: (b: B) => C,
  32. f3: (a: A) => B,
  33. f4: Func<T, A>
  34. ): Func<T, R>
  35. /* rest */
  36. export default function compose<R>(
  37. f1: (a: any) => R,
  38. ...funcs: Function[]
  39. ): (...args: any[]) => R
  40. export default function compose<R>(...funcs: Function[]): (...args: any[]) => R
  41. export default function compose(...funcs: Function[]) {
  42. if (funcs.length === 0) {
  43. // infer the argument type so it is usable in inference down the line
  44. return <T>(arg: T) => arg
  45. }
  46. if (funcs.length === 1) {
  47. return funcs[0]
  48. }
  49. //从右向左开始计算
  50. return funcs.reduce((a, b) => (...args: any) => a(b(...args)))
  51. }
  52. //compose(fn1, fn2, fn3)(1);
  53. // 等同于 fn1(fn2(fn3(1)));

解析
  1. const add1 = (x:string) => x + "1";
  2. const add2 = (x:string) => x + "2";
  3. const add3 = (x:string) => x + "3";
  4. // export default function compose(): <R>(a: R) => R
  5. console.log(compose()('jack')); //"jack" R为 string 类型
  6. // export default function compose<F extends Function>(f: F): F
  7. console.log(compose(add1)('jack')); //"jack1"
  8. // f 就是 add1。 F 是Function的子类型
  9. /**
  10. type Func<T extends any[], R> = (...a: T) => R
  11. export default function compose<A, T extends any[], R>(
  12. f1: (a: A) => R, //f1就是add1
  13. f2: Func<T, A> //f2就是add2
  14. ): Func<T, R>
  15. */
  16. console.log(compose<string, any[], string>(add1, add2)('jack')); //"jack21"
  17. // Func<T, R> T=["jack"] R="jack21"
  18. // compose() 返回一个函数,假设为fn,
  19. // fn参数的类型是 T(any[]),fn返回值的类型是 R(这里R是string)
  20. // f2: Func<T, A> T=["jack"] A="jack2"
  21. // f2参数的类型是 T(any[]);f2返回值的类型是 A,f2 的返回值就是f1的参数
  22. // f1: (a: A) => R, A="jack2" R="jack21"
  23. // f1参数的类型 是 A;f1返回值的类型是 R

泛型类名别名

泛型类型别名可以表发更复杂的类型。

  1. type Cart<T> = {list:Array<T>} | Array<T>;
  2. let c1:Cart<string> = {list: ['1', '2']}
  3. let c2:Cart<number> = [1,2,3]

泛型接口 vs 泛型类型别名

  • 接口创建了一个新的名字,它可以在其它任意地方被调用。而类型别名并不创建新的名字,例如报错信息就不会使用别名
  • 类型别名不能被 extends 和 implements,这时我们应该尽量使用接口代替类型别名
  • 当我们需要使用联合类型或者元祖类型的时候,类型别名会更合适