TypeScript全解:泛型编程(上)

什么是泛型?
泛,指多
简单来说就是多种类型

只要你能看懂 JS 的函数,那么你就能看懂 TS 的泛型

JS:

const fn = (a, b) => a + b
const result = fn(1,2) // 3

TS:

type Fn<A, B> = A | B
type Result = Fn<string, number> // string | number

很像把,格式上看起来一模一样,非常简单

思考:函数的本质是什么?

不知道大家平时在写函数的时候,有没有想过这个问题?

  • 复用代码?那我声明就了一次,也没复用啊
  • 抽离逻辑?为了代码更好看?
  • 输入输出?这些目的是什么?不用函数我也能输入输出

个人认为:函数的本质是退后执行的、部分待定的代码

console.log(1)

当这部分代码被解析的时候, console 就已经执行了,那我们想晚一点执行呢?用函数

const fn = () => console.log(1)
setTimeout(() => fn(), 2500)

所以函数的本质之一就是:在我们这种面相过程的编程里面,把执行时机往后推迟

再继续看,还是这段代码,这里的 fn 函数体非常死板,只能打印出 1,于是我们改造:

const fn = (x) => console.log(x)

fn(222)
fn(333)

所以函数的第二个特点就是部分待定的代码,其实还是往后推迟,这里的 console 我不想立马确定,我希望能想什么时候执行就什么时候执行,想 log 几就 log 几

举一反三:泛型的本质

泛型的本质是退后执行的、部分待定的类型

实例

function echo(n: number | string | boolean) {
  return n
}

实际上我们希望这个函数的类型是传入什么类型就返回什么类型

  • echo(n: number) => number
  • echo(n: string) => string
  • echo(n: boolean) => boolean

很遗憾,如果不用泛型做不到,即使你函数里面加了判断

function echo(n: number | string | boolean) {
  if (typeof n === 'number'){
    return n
  } else if (typeof a === 'string') {
    return n
  } else {
    return n
  }
}

依然存在错乱的情况

没有泛型,有些奇怪的需求就无法满足
没有泛型的类型系统,就如同 JS 没有函数

泛型-简单难度

type Union<A, B> = A | B
type Intersect<A, B> = A & B

type List<T> = { // 就像函数一样
  [key: string]: T
}

type Person = { name: string; age: number; }
List<Person> // 能匹配下面这些数据 
{
  a: Person,
  b: Person,
  ...
}

默认值

既然函数可以有默认值,泛型能有么?当然有

type List<T = string> = {
  [key: string]: T
}

泛型-中等难度

来看一道题目

type Person = { name: string; }

type LikeString<T> = ??? // 如果这个 T 是 string,则返回 true,否则 false
type LikeNumber<T> = ??? //如果这个 T 是 number,则返回 true,否则 false 
type LikePerson<T> = ??? // 如果这个 T 是 Person,则返回 true,否则 false

type R1 = LikeString<'hi'> // true
type R2 = LikeString<true> // false
type S1 = LikeNumber<666> // 1
type S2 = LikeNumber<false> // 2
type T1 = LikePerson<{ name: 'hi', xxx: 1 }> // yes
type T2 = LikePerson<{ xxx: 1 }> // no

我们先想一下如果在 JS 中怎么实现

const likeString = a => typeof a === 'string' ? true : false

那么在 TS 中怎么实现呢?

type LikeString<T> = T extends string ? true : false

疑惑点来了,为何 TS 中的判断要用 extends,为什么不能像 JS 那样使用 ===,多有语义化

其实这跟类型兼容、父类型、子类型有关系,在类型中很多时候我们做不到完全一致,因为集合与集合之间相等的时候是很少的,大多时候是从属关系,所以我们把这里的 extends 我更建议读作包含于

剩下的实现就很简单了:

type LikeNumber<T> = T extends number ? 1 : 2
type LikePerson<T> = T extends Person ? 'yes' : 'no'

其中会有这几条规则

  1. 若 T 为 never,则表达式的值为 never
  2. 若 T 为联合类型,则分开计算

第一条规则:

type R1 = LikeString<never> // never

违背常理的事情又发生了,三元表达式竟然多计算出了一种结果!竟然返回了 never!

第二条规则:

type ToArray<T> = T extends unknown ? T[] : never

type Result = toArray<string | number>
思考:此时 Result 的类型是什么
  1. Result 为 (string | number)[]
  2. Result 为 string[] | number[]

答案是 2,为 string[] | number[],即为:

type Result = toArray<string | number>

type Result = string extends unknown ? string[] : never
               |
              number extends unknown ? number[] : never

为什么 TS 会这么推导呢?一开始我也很不理解,直至后来我用函数来理解

伪代码:

function fn(a: T): ToArray<T> {
  if (type a === 'string') {
    return string[] 
  } else if (typeof a === 'number') {
    return number[]
  }
}

实际上我们经常会写出这样的函数,根据 a 的类型来判断返回值的类型,所以 TS 的预判是正确的

由此可推,上面返回的 never 也是合理的:

function fn(a: T): ToArray<T> {
  if (type a === 'string') {
    return string[] 
  } else if (typeof a === 'number') {
    return number[]
  } else {
    return never // TODO: 注意看这里,你传了一个 never 进来,自然而然就是返回 never
  }
}

注意上述两条规则只对泛型有效(上面的代码也一直在用函数的思路来举例)

type X = never extends unknown ? 1 : 2 // 1

在加大一点难度

获取对象的 key

type Person = { name: string; age: number; }

type GetKeys<T> = keyof T

type Result = GetKeys<Person> // name | age

判断两个类型是否相等

type Eq<A, B> = A extends B ? B extends A ? true : false : false

思路就是判断 A 是否包含于 BB 是否包含于 A,那么为 true,否则为 false

很遗憾,不完全对,看起来很正确,其实因为 分开计算 的规则下,得到的并不是这样的结果

获取对象的 value

type Person = { name: string; age: number; }
type GetKeyType<T, K extends keyof T> = T[K]

type Result = GetKeyType<Person, 'name'> // string
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容