使用 Partial 设置可变的默认参数
当一个函数中有默认参数的时候,若此参数是一个对象,那么我们需要对这个对象的每一个field 都设置类型。而且当新增 field 的时候,还需要在多个地方进行新增。
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};// todo: do something}
遇到这种情况的时候,我们可以使用 Partial 来进行精简:
const defaultOptions = {from: "./src",to: "./dest",overwrite: true,};function copy(options: Partial<typeof defaultOptions>) {const allOptions = { ...defaultOptions, ...options};// todo: do something}
这样,就可以在保证类型安全的前提下进行随意的扩展。这时我们只需要关注唯一的一个数据源 defaultOptions,这是唯一实际出现在运行时的对象。这样书写能减轻 TS 的侵入性。
Tuple to union
在 const 上下文、typeof 操作符以及索引存取符的作用下,可以将 tuple 转化成 union。
const categories = ["beginner","intermediate","advanced",] as const;// "beginner" | "intermediate" | "advanced"type Category = (typeof categories)[number]
同样的,这时候我们只需要维护一份数据,可以在 categories 中进行增减操作。
用类型进行逻辑操作
假设我们有多种游戏类型,每种类型都有一个 kind field 来标识游戏类型,我们可以得到以下的表示方式:
// 类型公共的 fieldtype 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
我们可以借助 kind 属性来进行类型收窄判断
function printToy(toy: Toy) {switch(toy.kind) {case "boardgame":// todobreak;case "puzzle":// todobreak;case "doll":// todobreak;default:console.log(toy);}}
如果需要更为详尽的类型分类,我们会有以下新的类型:
type ToyKind = 'boardgame' | 'puzzle' | 'doll'type GroupedToys = {boardgame: BoardGame[];puzzle: Puzzle[];doll: Doll[];}
这时候,如果我们想新增一个游戏类型,比如说 VideoGame:
type VideoGame = ToyBase & {kind: 'videogame';system: 'Switch' | 'Wii' | 'PS5'}
这时候,我们需要同时对 Toy、ToyKind 和 GroupedToys 来进行修改。这时候我们可以使用 TS 内置的函数来降低维护成本。
// ToyKind 可以使用另一种表示方式type ToyKind = Toy['kind'];
下一步,我们可以构造一个类型推导函数来对具体类型进行推导:
type GetKind<Group, Kind> = Extract<Group, { kind: Kind }>// 这时,GroupedToys 类型就可以自行进行推导type GroupedToys = {[Kind in ToyKind]: GetKind<Toy, Kind>[]}// 上面的写法就等效于type GroupedToys = {boardgame: BoardGame[];puzzle: Puzzle[];doll: Doll[];}
我们也可以使用 as 操作符来对属性进行修改:
type GroupedToys = {[Kind in ToyKind as `${Kind}s`]: GetKind<Toy, Kind>[]};// 等效于type GroupedToys = {boardgames: BoardGame[];puzzles: Puzzle[];dolls: Doll[];}
总结
通过上面的例子,我们可以总结出 ToyKind、GroupedToys都属于衍生类型,对于衍生类型我们需要尽可能的使用推导来产生,这样就可以减少我们需要维护的类型数量。通常,以下的几个要点可以帮助我们降低类型维护的复杂度:
- 使用模型来创建数据或者从已有类型中进行推导
- 定义衍生类型(mapped types 或者 Partials)
- 定义行为(条件判断)
