[Day02] TS: 泛型(Generics)能干嘛?

「泛型(Generics)」是 TypeScript 中很常会使用到的功能,泛型的概念简单来说,就是让「型别」也变成一个变数,可以根据不同的情况套用不同的型别。因为在强型别的语言中,不论变数或函式的回传值的型别,都会需要被加以定义。

举例来说,如果我们写一个能够回传阵列第一个元素的函式,我们会需要这样写:

function getFirstElementOfNumberArray(arr: number[]): number {
  const [firstElement] = arr;
  return firstElement;
}

你会发现,我们会需要定义这个函式所接受的参数型别,例如这里是 number[],且函式回传值的型别因为是阵列中的第一个元素,所以也会是 number

这时候我们可以这样用:

const firstElement = getFirstElementOfNumberArray([1, 9, 8, 8]); // 1

但你会发现因为型别定义的关系,这个函式只能接受 number[] 作为它的参数,如果你是想要取出 string[] 的话,因为型别合,TS 会报错,像是这样:

const firstElement = getFirstElementOfNumberArray([
  'Oh.So.Pro.',
  '就。很。Pro',
  '非常。Pro',
]);

你原本可能会预期它应该要取出阵列中的第一个元素给你,也就是「Oh.So.Pro.」,结果 TS 在编译时却发生错误:

TypeScript Generics

这个错误并不是因为我们在程序中使用了中文字,而是因为一开始我们定义 getFirstElement 这个函式,只能接受型别为 number[] 的参数,但现在我们却带入了型别为 string[] 的参数。

如果没有泛型的话,我们变成需要针对 string[] 的参数多写一个函式,取名为 getFirstElementOfStringArray 像是这样:

// 定义一个能够接受 string[] 作为参数的函式
function getFirstElementOfStringArray(arr: string[]): string {
  const [firstElement] = arr;
  return firstElement;
}

如此就能正确呼叫这个函式:

const firstElement = getFirstElementOfStringArray([
  'Oh. So. Pro.',
  '就很 Pro',
  '非常 Pro',
]);
console.log(firstElement); // "Oh. So. Pro."

这时候你会发现,在不管型别的情况下,getFirstElementOfNumberArraygetFirstElementOfNumberArray 这两个函式明明就内容是完全相同的,但却因为型别的限制,逼着我们要拆成两个不同的函式。

好在,在 TypeScript 中有泛型可以使用,前面有提到,泛型的概念其实就像是把型别也变成一个变数,回到上面这两个 function 来看,你会发现这两个函式完全只差在「型别」是不同的(下图粉色框框):

TypeScript Generics

因此这时候我们可以做一个抽象化的动作,把这个型别也变成一个变数,这里取名叫 T,就可以把这两个只有型别不同的函式整合成一个,变成这样:

function getFirstElement<T>(arr: T[]): T {
  const [firstElement] = arr;
  return firstElement;
}

如此在使用 getFirstElement 时,你可以把型别当成变数一样,放入 <T> 的 T 中,明确告诉 TypeScript 这时候的函式参数型别是什麽,像是这样:

TypeScript Generics

有了泛型的好处是,你将不在需要只因为型别的不同而要才成多个不同的函式。

更重要的是,一般来说,我们不需要明确在 <> 中告诉 TS 使用的参数型别是什麽,而是可以让 TS 自己推导(称作:type argument inference),因此,我们更常直接这样写:

// TS 会自动根据带入的参数推导这里的 T 是 number
const firstElement1 = getFirstElement([1, 9, 8, 8]);

// TS 会自动根据带入的参数推导这里的 T 是 string
const firstElement2 = getFirstElement([
  'Oh.So.Pro.',
  '就。很。Pro',
  '非常。Pro',
]);

当我们把滑鼠移到函式上方时,可以看到 TS 自动推导的结果:

TypeScript Generics

Type Alias 也能使用泛型

除了在函式中可以使用泛型外,在 Type Alias 中也可以使用泛型的概念。

假设一般来说 age 的型别是 number

type Person = {
  age: number;
};

const john: Person = {
  age: 20,
};

但某种形况下,age 会用中文字来表示,例如「二十」,这时候你会发现再次发生型别的错误,因为我们试图把 string 带入型别为 number 的栏位中:

TypeScript Generics

这时候,如果没有泛型的话,将只能像这样建立两个不同的型别:

type PersonWithNumberAge = {
  age: number;
};

type PersonWithStringAge = {
  age: string;
};

但有了泛型的话,更好的方式就是使用泛型,像这样:

type Person<T> = {
  age: T;
};

使用时就可以这样:

const john1: Person<number> = {
  age: 30,
};

const john2: Person<string> = {
  age: '二十',
};

由於 type alias 并不像函式一样有 type argument inference,因此需要明确把型别定义在 <> 中,如上面的 <number><string>

透过泛型,能够把型别当成变数使用,是不是非常方便啊!

范例程序码

https://tsplay.dev/m3z43N @ TypeScript Playground

参考资料


<<:  方丈的安全观 Day1

>>:  day17 : kafka服务应用 on K8S (上)

[Day2] 安装 Rust

$ curl https://sh.rustup.rs -sSf | sh -- -y 需要 GCC...

[Day-28] Node.js (google api auth)

[Day28] 说明:google提供的帐号API 一、设定google console的oauth...

CMoney菁英软件工程师战斗营之游戏专题发表_Week 10

阿罗哈~ 现在心情之放松 原因是游戏专题告一段落拉 排名会在下周公布 保佑我们可以在中间以上的名次就...

Day 25 - 当AI有了常识之後, 超越人类? -GAN(1)

当AI有了常识... 深度学习领域的巨擘,同时也是Facebook的AI研究院长杨立昆(Yann L...

风险描述(risk descriptions)

根据ISO 31000,风险是“不确定性对目标的影响(effect of uncertainty o...