深入理解Typescript系列-泛型

487 阅读3分钟

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

前言

在我们的日常开发中,时长会考虑到方法的复用性,毕竟对于相似的场景拷贝一份代码既增加了代码量还增加了额外的维护成本,要是重构的时候忘了,还会导致不可描述的bug。

初探泛型

我们先来看这么一段代码:

function identity(arg: number): number {
    return arg;
}

这个函数会返回任何传入它的值。但是只能输入number类型。那我们把它改造一下,支持任意类型呢?

function identity(arg: any): any {
    return arg;
}

这时候,返回值为any了,这明显会不合理。针对这个场景,我们的想法是返回值类型是根据输入值类型确定的,我们引入泛型,可以这么写

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

我们给identity添加了类型变量T。 T帮助我们捕获用户传入的类型(比如:number),之后我们就可以使用这个类型。 之后我们再次使用了 T当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。 这允许我们跟踪函数里使用的类型的信息。

我们把这个版本的identity函数叫做泛型,因为它可以适用于多个类型。 不同于使用 any,它不会丢失信息,像第一个例子那像保持准确性,传入数值类型并返回数值类型。

泛型类型

假设我们这时候需要对string类型的值做一些特殊判断,那该怎么做呢?TS是支持类型的动态推导的,可以可以这么写:

function identity<T>(arg: T): T {
    if (typeof arg === 'string') {
        // arg类型自动推导为arg
        return arg + 'x';
    }
    return arg;
}

react中的泛型

如果平常用TS写过react,应该已经接触过泛型了,可能不知道这叫泛型:

interface State {

}
interface Props {
    
}
class App extends React.Component<State, Props> {

}

由于React的Component的开发者不清楚使用者会如何使用,所以在设计类的时候就支持了泛型。通过传入State和Props的类型,可以在类的实现过程中可以获取到状态的类型。

泛型约束

有时我们可能希望限制每个类型变量接受的类型数量,这就是泛型约束的作用。

泛型约束的另一个常见的使用场景就是检查对象上的键是否存在。不过在看具体示例之前,我们得来了解一下 keyof 操作符。

interface Person {
  name: string;
  age: number;
  location: string;
}

type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[];  // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person };  // string | number

在泛型里使用类类型

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

function create<T>(c: {new(): T; }): T {
    return new c();
}

小结

泛型的本质是做运行时的replace all,通过动态的检查,可以动态进行类型推导校验,降低重复代码,提升整体代码的复用性。泛型是对类型进行编程,参数是类型,返回值是一个新的类型。我们甚至可以对泛型的参数进行约束,就类似于函数的类型约束。