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)化:
- 使用內(nèi)置類型
Partial<T>
來獲得與PartialOptions
類型相同的效果 - 利用
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 的編寫方式
類似的例子:使用 const
和 tyoeof
運算符可以將元組轉(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 類型改變時 ToyKind
和 GroupedToys
會自動進行維護骂因。
還可以進一步優(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)建低維護類型的方法:
- 為你的數(shù)據(jù)建模或從現(xiàn)有模型中推斷
- 定義派生類(映射類型绸栅、Partials 等)
- 定義行為(條件)
本文摘錄自:TypeScript in 50 Lessons