查看原文
其他

看到 TypeScript 泛型就头晕?给这是我开的方子!

semlinker 全栈修仙之路 2021-01-15
前言
对于 TypeScript 的初学者来说,泛型是相对比较难理解并掌握的知识。因为其中大部分人已经习惯了弱类型的 JavaScript,一下子使用强类型的开发语言,需要完成一定的思维转换。本文将使用通俗易懂例子,来全面介绍泛型的相关知识,如泛型约束、条件类型、泛型工具类等

阅读须知:本文示例的运行环境是 TypeScript 官网的 Playground,对应的编译器版本是 v3.8.3

一、泛型简介

软件工程中,我们不仅要创建一致的定义良好的 API,同时也要考虑可重用性。组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。

在像 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。这样用户就可以以自己的数据类型来使用组件。

设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值。

二、为什么要使用泛型

假设我们现在有一个 merge 工具方法,用于合并两个对象的属性,它的实现很简单:

function merge(objA, objB) {
return Object.assign(objA, objB);
}

创建完 merge 方法,我们可以这样使用:

const mergedObj = merge({ name: "Semlinker" }, { age: 30 });

// type MergeReturnType = (objA: any, objB: any) => any
type MergeReturnType = typeof merge;
// type MergedType = any
type MergedType = typeof mergedObj

通过观察以上信息,我们可以发现合并后 mergedObj 对象的类型是 any。在 TypeScript 中,任何类型都可以被归为 any 类型。any 类型本质上是类型系统的一个逃逸舱。作为开发者,这给了我们很大的自由:TypeScript 允许我们对 any 类型的值执行任何操作,而无需事先执行任何形式的检查。当然自由太大也是不好的,这让我不禁想起,"情歌王子" 张信哲作品 <<过火>> 中的歌词:

怎么忍心怪你犯了错 是我给你自由过了火

让你更寂寞 才会陷入感情漩涡

怎么忍心让你受折磨 是我给你自由过了火

如果你想飞 伤痛我揹

TypeScript 开发团队,也怕给 any 类型的自由过了火,在 TypeScript 3.0 引入了新的 unknown 类型,它是 any 类型对应的安全类型。相比 any 类型,unknown 类型会更加严格,在对 unknown 类型的值执行大多数操作之前,我们必须进行某种形式的检查。

好的,下面让我们继续来分析 merge 方法存在的问题。很明显 merge 方法返回 any 类型,这并不是我们所希望的。那么如何解决这个问题呢?针对这个问题,我们可以使用泛型。现在我们来重构一下代码:

function merge<T, U>(objA: T, objB: U) {
return Object.assign(objA, objB);
}

在上面代码中,我们引入了 TU 两个类型变量,它是一种特殊的变量,只用于表示类型而不是值。类型变量可以自动帮助我们捕获用户传入的类型,之后我们就可以使用已捕获的类型。

对于 TU 类型变量不了解的小伙伴们,也不要着急,后面章节会有专门的讲解哟。

这时我们再来获取 merge 函数和 mergedObj 对象的类型:

const mergedObj = merge({ name: "Semlinker" }, { age: 30 });

// type MergeReturnType = <T, U>(objA: T, objB: U) => T & U
type MergeReturnType = typeof merge;

/*
* type MergedType = {
* name: string;
* } & {
* age: number;
* }
*/

type MergedType = typeof mergedObj;

观察以上结果,很明显此时 mergedObj 对象的类型已经不是 any 类型了,而是交叉类型。那么引入泛型后的  merge 方法就没有问题了么?非也,非也!请看以下代码:

const mergedObj = merge({ name: "Semlinker" }, 30);
console.log(mergedObj); // { name: "Semlinker" }

在上面代码中,我们使用了数值类型的数据作为参数调用了 merge 方法,而我们创建 merge 方法的最初目的是为了合并两个对象的属性,那么如何限制输入参数的类型呢?对于上述的 merge 方法来说,我们期望限制输入参数的类型是 object 类型。针对这个问题,我们就可以利用泛型约束,来解决这个问题。

三、extends 泛型约束

微信扫一扫付费阅读本文

    Preview the first 20% of the content for free.

    微信扫一扫付费阅读本文

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存