美文网首页
三、TypeScript 泛型

三、TypeScript 泛型

作者: 輪徊傷 | 来源:发表于2021-11-17 15:37 被阅读0次

什么是泛型

理解:泛型就是在编译期间不确定类型(广泛之意思),在调用时,由程序员指定的泛型具体指向什么类型。泛型在传统面向对象编程语言中是极为常见的,ts中当然也执行泛型。

通俗理解:泛型就是解决 类 接口 方法的重用性、以及对不特定数据类型的支持

泛型的好处

1. 函数和类可以轻松的支持多种类型,增强程序的扩展性

2. 不必写多条函数重载,冗长的联合类型声明,增强代码的可读性

3. 灵活控制类型之间的约束


泛型初识
需求:有个函数会返回任何传入它的值。

不用泛型的话,这个函数可能是下面这样:

function getData(value: string): string{
  return value;//传入什么返回什么
}

但是这样,这个函数我只能传字符串类型的参数给这个函数,如果想传数字类型,布尔类型呢。这个时候可以使用 any来实现

function getData(value: any): any{
  return value;//传入什么返回什么
}

any 的缺点:\color{red}{一个变量设置类型为any后,相当于该变量关闭了TS的类型检测,一般情况下不建议使用any}

如果想做到传入什么类型就返回什么类型,例如传入number就返回number,这时候就可以使用泛型

function getData<T>(value:T):T{
  return value;//传入什么返回什么
}
第一种是,传入所有的参数,包含类型参数:
getData<number>(123);
getData<string>("123");

第二种方法更普遍。利用了类型推论 – 即编译器会根据传入的参数自动地帮助我们确定T的类型:
getData(123);
getData("123");

泛型类型

一、泛型函数

泛型函数的类型与非泛型函数的类型没什么不同,只是有一个类型参数在最前面,像函数声明一样:

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

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

我们也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以。

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

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

我们还可以使用带有调用签名的对象字面量来定义泛型函数:

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

对象字面量的方式来定义泛型类型;
let myIdentity: {<T>(arg: T): T} = identity;
二、泛型接口

这引导我们去写第一个泛型接口了。 我们把上面例子里的对象字面量拿出来做为一个接口:

interface GenericIdentityFn {
    <T>(arg: T): T;
}

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

1、函数泛型的注解方式;
let myIdentity: <U>(arg: U) => U = identity;
2、对象字面量的方式来定义泛型类型;
let myIdentity: {<T>(arg: T): T} = identity;
3、接口泛型的注解方式;
let myIdentity: GenericIdentityFn = identity;

想把泛型参数当作整个接口的一个参数。
这样我们就能清楚的知道使用的具体是哪个泛型类型

interface GenericIdentityFn<T> {
    (arg: T): T;
}

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

let myIdentity: GenericIdentityFn<number> = identity;

例子:

interface GenericIdentityFn{
  (value1:string, value2:string):string;
}

function identity(value1:string, value2:string):string{
  return value1 + value2;
}

let myIdentity: GenericIdentityFn = identity
myIdentity("name", "张三")

泛型实现上面函数
interface GenericIdentityFn {
    <T>(arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}
let myIdentity: GenericIdentityFn = identity;
三、泛型类

泛型类看上去与泛型接口差不多。 泛型类使用( <> )括起泛型类型,跟在类名后面。

class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

使用 number 类型
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) { return x + y; };
console.log(myGenericNumber.add(1, 2))

也可以使用字符串或其它更复杂的类型。
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "第一次    ";
stringNumeric.add = function (x, y) { return x + y; };
console.log(stringNumeric.add(stringNumeric.zeroValue, "test!!!"));

示例:

普通类

class MinClass {
  public list: number[] = [];
  add(num: number) {
    this.list.push(num);
  }
  min(): number {
    var minNum = this.list[0];
    for (var i = 0; i < this.list.length; i++) {
      if (minNum > this.list[i]) {
        minNum = this.list[i];
      }
    }
    console.log(this.list);
    return minNum;
  }
}
// 调用
var m = new MinClass();
m.add(3);
m.add(2);
m.add(4);
console.log(m.min());// 2

上边的只能传入数字类型,使用泛型实现上面的 类

class MinClass<T>{
  public list: T[] = [];
  add(num: T): void {
    this.list.push(num);
  }
  min(): T {
    var minNum = this.list[0];
    for (var i = 0; i < this.list.length; i++) {
      if (minNum > this.list[i]) {
        minNum = this.list[i];
      }
    }
    return minNum;
  }
}
// 调用,实例化时候要先声明参数类型<bumber
var m = new MinClass<number>();
m.add(2);
m.add(4);
m.add(3);
console.log(m.min());// 2
四、约束泛型

作某类型的一组值,并且我们知道这组值具有什么样的属性。这个时候我们想访问arg的length属性时,例如:

function loggingIdentity<T>(arg: T): T {
  console.log(arg.length)  // Property 'length' does not exist on type 'T'.
  return arg
}

想访问arg的length属性,但是编译器并不能证明每种类型都有length属性(比如说 number 类型、boolean 类型),所以就报错了。

这种情况可以使用泛型约束了

function loggingIdentity<T>(arg: T[]): T[] {
  console.log(arg.length)
  return arg
}

const arr = loggingIdentity([1, 2, 3])

上面代码中约束了传入的参数需要是任意类型的数组,但 Object,String类型都是有length属性的,这时候就不满足这种场景了。这时候需要对泛型进行约束,允许这个函数传入包含length属性的变量(约束泛型):

interface Lengthwise {
  length: number
}

// type Lengthwise = {
//   length: number
// }

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length)
  return arg
}

const len01 = loggingIdentity('abc') // 3
const len02 = loggingIdentity({ length: 12 }) // 12
const len03 = loggingIdentity([1, 2, 3]) // 3

上面代码中,定义了一个接口 Lengthwise ,在函数 loggingIdentity 的泛型中使用 extends 关键字继承了Lengthwise 接口,这样调用函数 loggingIdentity 时传的参数必须要有 length 这个属性

索引类型(keyof)
官方定义:keyof该操作符可以用于获取某种类型的所有键,其返回类型是联合类型(keyof的应用多第三方库的源码中常见)
声明一个类型参数,且它被另一个类型参数所约束。 比如,现在我们想要用属性名从对象里获取这个属性。 并且我们想要确保这个属性存在于对象obj上,因此我们需要在这两个类型之间使用约束。

function getProperty<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.

js中的Object.keys大家肯定都用过, 获取对象的键值, ts中的keyof和他类似, 可以用来获取对象类型的键值:

type A = keyof {a:1, b:'123'} // 'a'|'b'
type B = keyof [1, 2] // '1'|'2'|'push'...  获取到内容的同时, 还得到了Array原型上的方法和属性(实战中暂时没遇到这种需求, 了解即可)
可以获得键值, 也可以获取对象类型的值的类型:

type C = A['a'] // 等于type C = 1;
let c:C = 2 // 错误, 值只能是1

多重约束&交叉类型
示例1
交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。 例如,Person & Serializable & Loggable同时是PersonSerializableLoggable。 就是说这个类型的对象同时拥有了这三种类型的成员。

我们大多是在混入(mixins)或其它不适合典型面向对象模型的地方看到交叉类型的使用。 (在JavaScript里发生这种情况的场合很多!) 下面是如何创建混入的一个简单例子:

function extend<T, U>(first: T, second: U): T & U {
    let result = <T & U>{};
    for (let id in first) {
        (<any>result)[id] = (<any>first)[id];
    }
    for (let id in second) {
        if (!result.hasOwnProperty(id)) {
            (<any>result)[id] = (<any>second)[id];
        }
    }
    return result;
}

class Person {
    constructor(public name: string) { }
}
interface Loggable {
    log(): void;
}
class ConsoleLogger implements Loggable {
    log() {
        // ...
    }
}
var jim = extend(new Person("Jim"), new ConsoleLogger());
var n = jim.name;
jim.log();

示例2

interface Sentence {
  content: string;
  title: string;
}
interface Music {
  url: string;
}
class Test<T extends Sentence & Music>{
  props: T
  constructor(public arg: T) {
    this.props = arg
  }
  info() {
    return {
      url: this.props.url,
      content: this.props.content,
      title: this.props.title
    }
  }
}

let a = new Test({
  title: "泛型",
  content: "xasdjoasdoijasiodhoia",
  url: "aaaaaaaaaaaa"
})
console.log(a);

在泛型里使用类类型
在TypeScript使用泛型创建工厂函数时,需要引用构造函数的类类型。比如,

1、参数c的注解是一个对象,可以把它看成一个接口;
2、对象里面的属性是一个方法( new() ),方法的返回值是 (T)
        可以写成 function create<T>(c: new() => T): T { return new c(); }
3、有一个 new 方法说明这个方法是个构造方法,那么 T 就代表构造方法的实例
4、返回的是实例 T,所以需要调用 new方法来获取实例
function create<T>(c: {new(): T; }): T {
  return new c();
}

不要乱用泛型

泛型主要是为了约束, 或者说缩小类型范围, 如果不能约束功能, 就代表不需要用泛型:

function convert<T>(input:T[]):number{
    return input.length;
}

这样用泛型就没有什么意义了, 和any类型没有什么区别.

泛型的使用场景
大神的泛型讲解

相关文章

  • 03_TypeScript学习(三)

    一. TypeScript枚举类型 二. 枚举类型的值 三. 认识泛型 四. 泛型实现类型参数化 五. 泛型的基本...

  • 三、TypeScript 泛型

    什么是泛型 理解:泛型就是在编译期间不确定类型(广泛之意思),在调用时,由程序员指定的泛型具体指向什么类型。泛型在...

  • TypeScript(三) 泛型

    参考[https://ts.xcatliu.com/advanced/generics.html]参考2[http...

  • 2020-11-05Typescript(2.2)

    泛型 Generics---typeScript中最难的一部分 泛型 Generics---约束泛型 泛型 Gen...

  • TS 泛型+装饰器

    typescript 中的泛型 泛型的定义泛型函数泛型类泛型接口 泛型:软件工程中,我们不仅要创建一致的定义良好的...

  • bunny笔记|TS基础(2):泛型函数、泛型约束、在泛型约束中

    01 typescript的操纵类型 02 03 04 泛型约束 05 在泛型约束中使用类型参数 06 在泛型中使...

  • TypeScript 学习笔记4 泛型

    1.泛型 1.1 泛型函数 1.2 泛型类 1.3 泛型接口 Typescript从0到1-学习视频教程-培训课程...

  • typescript

    title: typescript学习tags: typescript学习 [toc] 泛型 基本使用 两种使用方...

  • TypeScript 泛型

    泛型函数 使用 数组 类 泛型约束

  • TypeScript泛型

    有时候编程还要考虑它的复用性,很多时候不需要指定它的类型,或者同样的方法逻辑 但是入参和出差的类型不同。这个时候就...

网友评论

      本文标题:三、TypeScript 泛型

      本文链接:https://www.haomeiwen.com/subject/khobzltx.html