Day21:【TypeScript 学起来】Generics 泛型

是说在社群到处都在爆雷的《鱿鱼游戏》,我进度第一集,好想弃赛来追剧(我就废
然後谢谢没看鱿鱼游戏,不小心看到我文章的你~(还是你已经看完了

没写很好,当作笔记记录,若有错误,欢迎留言指教,感恩的心。


泛型(Generics)是指在定义function、interfaces或class的时候,不预先指定具体的型别而在使用的时候再指定型别的一种特性。


什麽情况可以用?

在前面介绍 array 型别的时候,除了使用「型别 + 方括号」来表示 array, 也可以使用泛型方式来表示。 让我们复习一下:

//「型别 + 方括号」
const list1: number[] = [1, 2, 3];
//阵列泛型 
const list2: Array<number> = [1, 2, 3]; 

这个例子来看,我传入参数的型别为number阵列型别,我们可以使用Array<number>,也可以使用 number[], 返回值型别也是一样的数字阵列型别。 所以当传入[1, 2, 3]时是没有问题的。

function identity(arg: Array<number>): Array<number> {
  console.log(arg);
  return arg;
}

identity([1, 2, 3]); // [1, 2, 3]

function identity2(arg: number[]): number[] {
  console.log(arg);
  return arg;
}

identity2([1, 2, 3]); // [1, 2, 3]

如果我们希望他不仅能处理数字阵列,也能处理字串阵列,那该怎麽做呢? >> 我们可以使用泛型,不预先指定具体的型别,在使用的时候再去指定或让 compiler 自动推论。


泛型怎麽用?

在函式後面加上 <Type> 表示动态型别,<Type> 命名也是可以自行定义的,如<List>。只是 <T><Type>比较通用。

再指定 arg 参数为 Type[] 型别, 函式也回传 Type[] 型别,那就能依据传入型别去回传该型别资料。当然你也可以只写Type就可以了, 只是这个例子传入及输出型别都是阵列, 我们写Type[]会更严谨,只传入阵列型别。像如果是字串写Type就可以了。

function identity3<Type>(arg: Type[]): Type[] {
  return arg;
}

let output1 = identity3([1, 2, 3]); 
let output2 = identity3(["a", "b", "c"]);
console.log(output1);// [1, 2, 3]
console.log(output2); //["a","b","c"]

我们在调用函式的时候,可以将参数型别传给函式,如希望他是使用 <type> 如下方例子 identity3<string>(["a", "b", "c"]),指定参数型别为 string 。 或是让 TypeScript 自动推论(type argument inference )出来,这个方法最常见,当然选这个呀,方便 XD

let output3 = identity3<string>(["a", "b", "c"]); //指定泛型回传型别 `<type>`
let output4 = identity3(["a", "b", "c"]); //inference
console.log(output3); // [ 'a', 'b', 'c' ]
console.log(output4); // [ 'a', 'b', 'c' ]

多个参数情况

一样使用<T, U> 自定义,并对应到参数的型别就可以了。

function makeTuple<T, U>(tuple: [T, U]): [T, U] {
  return [tuple[0], tuple[1]];
}

const tuple1 = makeTuple([1, "a"]); 
console.log(tuple1); //[ 1, 'a' ]

顺便试看了 arrow function 写法 :

const makeTuple2 = <T, U>(tuple: [T, U]): [T, U] => {
  return [tuple[0], tuple[1]];
}

const tuple2 = makeTuple2([1, "a"]); 
console.log(tuple2); //[ 1, 'a' ]

⚠ arrow function 能在 .ts 但不能在 .tsx 中使用, 因为 tsx 中, TS compiler 会无法清楚区分 <> 是指 JSX 或 Generics Type, 所以当需要在函式中定义泛型时,就直接使用传统的 Function Statement。


Generic Constraints 泛型约束

在函式内部使用泛型变数的时候,由於事先不知道它是哪种型别,所以不能随意的操作它的属性或方法,如下方例子,泛型 T 不一定包含属性 length,所以编译的时候报错了。

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

//error:Property 'length' does not exist on type 'T'.

这时,我们可以对泛型进行约束,我们使用了 extends 约束了泛型 T 必须符合介面 Lengthwise 的形状,也就是必须包含 length 属性。下方例子, 如果我们带入的型别不符 Lengthwise 型别,就会报错提醒。

interface Lengthwise {
    length: number;
}

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

loggingIdentity2(3); //error: Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.
loggingIdentity2({ length: 10, value: 3 });


Using Type Parameters in Generic Constraints

可以指定受另一个型别约束的参数型别, 如 Key 收到 Type 的型别约束, 所以 key参数型别只能是 obj参数所定义的型别。

如下面例子 getProperty(x, "m")时, 就会报错提醒没有 m 参数。

function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
  return obj[key];
}
 
let x = { a: 1, b: 2, c: 3, d: 4 };
 
let value1 = getProperty(x, "a");
let value2 = getProperty(x, "m"); //error: Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.

console.log(value1); //1
console.log(value2); //undefined

Generic Interface

含有泛型的介面来定义函式的形状:

interface GenericIdentityFn {
  <Type>(arg: Type): Type;
}
 
function identity<Type>(arg: Type): Type {
  return arg;
}
 
let myIdentity: GenericIdentityFn = identity;

可以把泛型引数提前到介面名上:

interface GenericIdentityFn<Type> {
  (arg: Type): Type;
}
 
function identity<Type>(arg: Type): Type {
  return arg;
}
 
let myIdentity: GenericIdentityFn<number> = identity;

⚠ 在使用泛型介面的时候,需要定义泛型的型别。


Generic Classes

泛型也可以用於类别的型别定义中:

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

//限制为 number 型别
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
  return x + y;
};

感谢阅读, 明天见!


参考资料

https://willh.gitbook.io/typescript-tutorial/advanced/generics
https://www.typescriptlang.org/docs/handbook/2/generics.html
https://pjchender.dev/typescript/ts-generics#%E5%9F%BA%E6%9C%AC%E8%AA%9E%E6%B3%95


<<:  大共享时代系列_020_共同工作空间(Coworking Space)

>>:  Learn How to Print Multiple Outlook Messages to PDF?

Day15-Nginx 限制访问来源

Nginx 直接写在 config 内 location / { allow 127.0.0.0/2...

Day14 v-cloak与v-pre

今天再多来看看两个Vue的指令,v-cloak与v-pre v-cloak 使用v-cloak的原因...

# Day14--解决暧昧问题延伸出来的那些事

前言 可选在上一篇中,提到一个概念就是暧昧。它是一种可以让程序介於「有值」、「没有值」的中间状态,这...

[火锅吃到饱-7] 大喜锅 - 南屯店 - 平日午餐250元吃到饱 | 学生&小资族首选

又是忙到下午4点才吃第一餐的一天~ 赶在下午4点半之前进场都算午餐价,费用是250元(不收服务费),...

Day 1-开始上路罗~!

前言 主要籍由这个主题,熟悉永丰金融API的相关操作。 将系列文章 做一下规划 环境建置 API串接...