kd-cloud-web / Blog

一群人, 关于前端, 做一些有趣的事儿
13 stars 1 forks source link

typescript 泛型 #61

Open zzkkui opened 3 years ago

zzkkui commented 3 years ago

泛型

设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是

在更加一般化的场景,我们的类型可能并不固定已知,它和any有点像,只不过我们希望在any的基础上能够有更近一步的约束。其实就是在定义的时候不指定具体类型,用一个变量替代,使用时在指定类型替换泛型变量

常见:Array<String>

例如:

// T 是一个抽象类型,只有在调用的时候才确定它的值
function reverse<T>(items: T[]): T[] {
    var toreturn = [];
    for (let i = items.length - 1; i >= 0; i--) {
        toreturn.push(items[i]);
    }
    return toreturn;
}

泛型类型

// 泛型函数
function identity<T>(arg: T): T {
  return arg;
}

// 普通接口
interface GenericIdentityFn {
// 泛型函数
  <T>(arg: T): T;
}

// 泛型接口
interface GenericIdentityFn1<T> {
  (arg: T): T;
}

let myIdentity = identity;
let myIdentity1: <T>(arg: T) => T = identity;
let myIdentity2: <U>(arg: U) => U = identity;
// {<T>(arg: T): T} 接口
let myIdentity3: { <T>(arg: T): T } = identity;
let myIdentity4: GenericIdentityFn = identity;
// 将泛型参数当作接口的参数
let myIdentity5: GenericIdentityFn1<number> = identity;

泛型类

// 泛型类
class GenericNumber<T> {
// 断言
//    zeroValue!: T;
//    add!: (x: T, y: T) => T;
    zeroValue: T | undefined;
    add: (x: T, y: T) => T = (x: any, y: any) => x + y;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

与接口一样,直接把泛型类型放在类后面,可以帮助我们确认类的所有属性都在使用相同的类型。

类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。

泛型约束

interface Lengthwise {
    length: number;
}

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

现在这个泛型函数被定义了约束,因此它不再是适用于任意类型。我们需要传入符合约束类型的值,必须包含必须的属性(length),如果不约束,arg.length 会报错

在泛型里使用类类型

class ZooKeeper {
    nametag!: string;
}

class Animal {
    numLegs!: number;
}

class Lion extends Animal {
    keeper: ZooKeeper = new ZooKeeper();
}

function createInstance<A extends Animal>(C: new () => A): A {
    return new C();
}

createInstance(Lion).keeper.nametag; 

综合例子

// 创建一个泛型类
class Queue<T> {
  private data: T[] = [];
  push = (item: T) => this.data.push(item);
  pop = (): T | undefined => this.data.shift();
}

// 简单的使用
// 泛型变量需要外部指定
const queue = new Queue<number>();
queue.push(0);
queue.push('1'); // Error:不能推入一个 `string`,只有 number 类型被允许

function reverse<T>(items: T[]): T[] {
  const toreturn = [];
  for (let i = items.length - 1; i >= 0; i--) {
    toreturn.push(items[i]);
  }
  return toreturn;
}

const sample = [1, 2, 3];
// 这里泛型是指定了 number 类型
let reversed = reverse(sample);

reversed[0] = '1'; // Error
reversed = ['1', '2']; // Error

reversed[0] = 1; // ok
reversed = [1, 2]; // ok

泛型是对类型进行编程

区别于平时我们对值进行编程,泛型是对类型进行编程。

例如常见类型转换:Partial

type Required<T> = { [P in keyof T]-?: T[P] };

这里其实就是接收一个类型,经过处理后返回一个新的类型。

// 这里 <T, U> 可以看作是形参,后面使用类型就是使用这个形参
function ids<T, U>(arg1: T, arg2: U): [T, U] {
  return [arg1, arg2];
}

类型推导与默认参数

泛型也支持类型推导和默认参数

function id<T>(arg: T): T {
  return arg;
}
id<string>("lucifer"); // 完整的写法
id("lucifer"); // 基于类型推导的简写
// 泛型变量 T 默认参数 string
type A<T = string> = Array<T>;
const aa: A = [1]; // type 'number' is not assignable to type 'string'.
const bb: A = ["1"]; // ok
const cc: A<number> = [1]; // ok

什么时候用泛型

进阶

// 官方 HTMLElement 的定义 declare var HTMLElement: { prototype: HTMLElement; new(): HTMLElement; }



参考链接:
https://lucifer.ren/blog/2020/06/16/ts-generics/
https://jkchao.github.io/typescript-book-chinese/typings/generices.html#%E5%8A%A8%E6%9C%BA%E5%92%8C%E7%A4%BA%E4%BE%8B