构建 Typescript 知识体系(五)- 泛型的类/接口/函数

这是我参与更文挑战的第十一天,活动详情查看:更文挑战

背景: 很多时候,我们希望一个函数或者类可以支持多种数据类型

栗子, 一个打印函数的改造: 函数会返回任何传入它的值

function log(value: string): string {
  console.log(value);
  return value;
}
复制代码

1.改造为接受一个数组参数

方式一:函数重载

function log(value: string): string;

function log(value: string[]): string[];

function log(value: any) {
  console.log(value);
  return value;
}
复制代码

方式二: 联合类型

function log(value: string | string[]): string | string[] {
  return value;
}
复制代码

2. 希望 log 函数可以接受任意类型的参数

方式一: any 类型

但是,丢失了类型之间的约束关系,忽略了参数类型与函数的返回值类型必须是一致的

function log(value: any): any {
  console.log(value);
  return value;
}
复制代码

方式二: 泛型 ?(查看官方文档)

1. 通过泛型来定义函数-泛型函数

概念: 不预先确定数据类型,具体的类型在使用的时候才能确认

function log<T>(value: T): T {
  return value;
}

//调用方式一: 在调用的时候 指定 T的类型
console.log(
  log<string[]>(["a", "b"]),
);

// 调用方式二:  利用TS的类型推断,省略 函数的参数类型 --- 推荐
console.log(log(["a", "b"]));
复制代码

2. 通过泛型来定义函数类型–泛型函数类型

泛型可以定义一个函数,也可以定义一个函数类型

function log<T>(value: T): T {
  return value;
}

/*
类型别名-------------

使用类型别名定义一个泛型函数类型,
等号后面跟函数签名差不多, 但是要把函数的名称去掉
*/
type Log = <T>(value: T) => T;

// 泛型函数实现
let myLog: Log = log;
复制代码

function identity<T>(arg: T): T {
  return arg;
}

let myIdentity: <T>(arg: T) => T = identity;

let myIdentity1: <U>(arg: U) => U = identity;
复制代码

3.泛型接口

在这里,泛型仅仅约束了一个函数,但我们也可以用泛型来约束接口的其他成员

// 和类型别名的方式是完全等价的
interface Log {
  <T>(value: T): T;
}
复制代码

用泛型来约束接口的其他成员: 把泛型放在接口名称的后面,这样接口的所有成员都能受到泛型的约束了

interface Log<T> {
  (value: T): T;
}
复制代码

注:泛型变量约束了整个接口后,在实现的时候,必须指定一个类型

function log<T>(value: T): T {
  return value;
}

interface Log<T> {
  (value: T): T;
}

// 错误提示:  泛型类型“Log<T>”需要 1 个类型参数。ts(2314)
let myLog: Log = log;

// 解决
let myLog1: Log<number> = log;
// myLog1的参数只能是 number
myLog1(1);
复制代码

也可以在接口的定义中指定一个默认类型

// 指定默认类型
interface Log<T = Array<string>> {
  (value: T): T;
}

let myLog: Log = log;

myLog(["1", "2"]);
复制代码

泛型也可以约束类的成员

泛型类

把泛型变量放在类的名称后面,就可以约束所有类的成员了
注意:泛型不能约束类的静态成员

class Test<K> {
  // 错误提示:  静态成员不能引用类类型参数。ts(2302)
  // static eat(param:T){

  // }

  run(value: K) {
    return value;
  }
}
复制代码

实例的方法将会受到泛型的约束

// 实例化
let log1 = new Test<number>();

// 错误提示: 类型“"a"”的参数不能赋给类型“number”的参数。ts(2345)
// log1.run('a')

log1.run(1);
复制代码

实例化时可以不传入类型参数
当没有指定参数时, 实例方法的参数类型可以是任意的

let log2 = new Test();

log2.run("a");

log2.run(1);
复制代码

泛型约束

function testA<T>(value: T): T {
  // 类型“T”上不存在属性“length”。ts(2339)
  console.log(value, value.length);

  return value;
}
复制代码

解决: T 继承 Length 接口,表示 T 受到了约束,即输入的参数必须具有 length 属性

interface Length {
  length: number;
}

function testA<T extends Length>(value: T): T {
  console.log(value, value.length);

  return value;
}

testA([1]);

testA("12212");

testA({ length: 1 });

/*
错误提示: 
类型“{ a: number; }”的参数不能赋给类型“Length”的参数。
  对象文字可以只指定已知属性,并且“a”不在类型“Length”中。ts(2345)
*/
// testA({a:1})
复制代码

泛型的好处

  1. 函数和类可以轻松地支持多种类型,增强程序的扩展性
  2. 不必写多条函数重载,冗长的二联合类型声明,增强代码可读性
  3. 灵活控制类型之间的约束
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享