TypeScript: 低維護類型

typescript 具有類型推斷能力翅溺,所以在 typescript 中編寫常規(guī)的 JavaScript 時很多類型可以推斷出來肮砾,不需要明確指定類型诀黍。但有些情況下又必須要添加類型注釋,隨之帶來維護類型的煩惱仗处。

創(chuàng)建一種低維護類型眯勾,也就是該類型在他的依賴或環(huán)境發(fā)生變化時自我更新的類型。

場景 1: 信息已經(jīng)可用

在 JavaScript 中經(jīng)称攀模看到如下模式吃环。

const defaultOptions = {
  from: './src',
  to: './dest'
}

function copy(options) {
  // 合并默認選項
  const allOptions = { ...defaultOptions, ...options }

  // do something...
}

在 ts 中需要顯式創(chuàng)建類型:

type Options = {
  from: string
  to: string
}

const defaultOptions: Options = {
  from: './src',
  to: './dest'
}

type PartialOptions = {
  from?: string
  to?: string
}

function copy(options: PartialOptions) {
  // 合并默認選項
  const allOptions = { ...defaultOptions, ...options }

  // do something...
}

這種是我們最常使用的方式。但是洋幻,假設(shè)向 Options 中添加一個字段時就不得不改動三處代碼:

type Options = {
  from: string
  to: string
  overwrite: boolean
}

const defaultOptions: Options = {
  from: './src',
  to: './dest',
  overwrite: true
}

type PartialOptions = {
  from?: string
  to?: string
  overwrite?: boolean
}

其實defaultOptions提供給我們的信息已經(jīng)夠用了郁轻。下面進行優(yōu)化:

  1. 使用內(nèi)置類型Partial<T>來獲得與PartialOptions類型相同的效果
  2. 利用 typeof運算符動態(tài)創(chuàng)建新類型
const defaultOptions = {
  from: './src',
  to: './dest',
  overwrite: true
}

function copy(options: PartialOptions) {
  // 合并默認選項
  const allOptions = { ...defaultOptions, ...options }

  // do something...
}

優(yōu)勢:

  • 如果添加新字段,完全不需要維護其他東西
  • 只有一個單一的信息來源:defaultOptions 對象文留,這是運行時擁有的唯一信息
  • 不僅代碼簡潔好唯,ts 也不具有很強的侵入性,更符合 js 的編寫方式

類似的例子:使用 consttyoeof 運算符可以將元組轉(zhuǎn)為聯(lián)合類型燥翅。

const categories = ['beginner', 'intermediate', 'advanced'] as const

// "beginner" | "intermediate" | "advanced"
type Category = typeof categories[number]

同樣骑篙,我們只維護一個 categories,即實際數(shù)據(jù)森书。轉(zhuǎn)換 categories 為元組類型并對每個元素進行索引替蛉。

場景 2:關(guān)聯(lián)模型

在大多數(shù)情況下,明確地處理類型和數(shù)據(jù)是有意義的拄氯。如下:

type ToyBase = {
  name: string
  price: number
  quantity: number
  minimumAge: number
}

type BoardGame = ToyBase & {
  kind: 'boardgame'
  players: number
}

type Puzzle = ToyBase & {
  kind: 'puzzle'
  pieces: number
}

type Doll = ToyBase & {
  kind: 'doll'
  material: 'plastic' | 'plush'
}

type Toy = BoardGame | Puzzle | Doll

ToyBase 類型擁有 BoardGame躲查、 Puzzle、 Doll 共有的屬性译柏,這三個類型又都擁有值不相同的 kind 屬性镣煮。Toy 為三個類型的聯(lián)合類型。

通過一下方式獲取某個類型的 kind

function printToy(toy: Toy) {
  switch (toy.kind) {
    case 'boardgame':
      // todo
      break
    case 'puzzle':
      // todo
      break
    case 'doll':
      // todo
      break
    default:
      console.log(toy)
  }
}

如果需要基于這些數(shù)據(jù)創(chuàng)建更多的類型鄙麦,比如:

type ToyKind = 'boardgame' | 'puzzle' | 'doll'

type GroupedToys = {
  boardgame: Toy[]
  puzzle: Toy[]
  doll: Toy[]
}

如果要基于 ToyBase 添加一個新類型VideoGame

type VideoGame = ToyBase & {
  kind: 'videogame'
  system: 'NES' | 'SNES' | 'Mega Drive' | 'There are no more consoles'
}

這時候又必須修改三個地方:

type Toy = BoardGame | Puzzle | Doll | VideoGame

type ToyKind = 'boardgame' | 'puzzle' | 'doll' | 'videogame'

type GroupedToys = {
  boardgame: Toy[]
  puzzle: Toy[]
  doll: Toy[]
  videogame: Toy[]
}

這樣大量的維護不僅繁瑣典唇,更容易出現(xiàn)拼寫錯誤】韪可以通過 ts 的內(nèi)置類型進行優(yōu)化介衔。

首先通過直接訪問類型的方式創(chuàng)建一個包含所有 kind 類型組成的聯(lián)合類型。

type ToyKind = Toy['kind']

然后使用映射類型創(chuàng)建 GroupedToys

type GroupedToys = {
  [Kind in ToyKind]: Toy[]
}

這樣當 Toy 類型改變時 ToyKindGroupedToys 會自動進行維護骂因。

還可以進一步優(yōu)化炎咖,先了解下內(nèi)置類型 Extract<T, U>,該類型是提取聯(lián)合類型 T 和聯(lián)合類型 U 的所有交集。通俗地說:從聯(lián)合類型中提取指定的類型乘盼。

type GetKind<Group, Kind> = Extract<Group, { kind: Kind }>

type DebugOne = GetKind<Toy, 'doll'> // DebugOne = Doll
type DebugTwo = GetKind<Toy, 'puzzle'> // DebugTwo = Puzzle

應(yīng)用于 GroupedToys

type GroupedToys = {
  [Kind in ToyKind]: Extract<Toy, { kind: Kind }>[]
}

// 等價于

type GroupedToys = {
  boardgame: BoardGame[]
  puzzle: Puzzle[]
  doll: Doll[]
}

GroupedToys 的屬性應(yīng)該是復數(shù)升熊,通過類型斷言增加 s

type GroupedToys = {
  [Kind in ToyKind as `${Kind}s`]: Extract<Toy, { kind: Kind }>[]
}

// 等價于

type GroupedToys = {
  boardgames: BoardGame[]
  puzzles: Puzzle[]
  dolls: Doll[]
}

總結(jié):

創(chuàng)建低維護類型的方法:

  1. 為你的數(shù)據(jù)建模或從現(xiàn)有模型中推斷
  2. 定義派生類(映射類型绸栅、Partials 等)
  3. 定義行為(條件)

本文摘錄自:TypeScript in 50 Lessons

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末级野,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子粹胯,更是在濱河造成了極大的恐慌蓖柔,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件风纠,死亡現(xiàn)場離奇詭異渊抽,居然都是意外死亡,警方通過查閱死者的電腦和手機议忽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門懒闷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人栈幸,你說我怎么就攤上這事愤估。” “怎么了速址?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵玩焰,是天一觀的道長。 經(jīng)常有香客問我芍锚,道長昔园,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任并炮,我火速辦了婚禮默刚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘逃魄。我一直安慰自己荤西,他們只是感情好,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布伍俘。 她就那樣靜靜地躺著邪锌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪癌瘾。 梳的紋絲不亂的頭發(fā)上觅丰,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機與錄音妨退,去河邊找鬼妇萄。 笑死蜕企,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的嚣伐。 我是一名探鬼主播糖赔,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼萍丐,長吁一口氣:“原來是場噩夢啊……” “哼轩端!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逝变,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤基茵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后壳影,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拱层,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年宴咧,在試婚紗的時候發(fā)現(xiàn)自己被綠了根灯。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡掺栅,死狀恐怖烙肺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情氧卧,我是刑警寧澤桃笙,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站沙绝,受9級特大地震影響搏明,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜闪檬,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一星著、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧粗悯,春花似錦强饮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至铭乾,卻和暖如春剪廉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背炕檩。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工斗蒋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捌斧,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓泉沾,卻偏偏與公主長得像捞蚂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子跷究,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容