問題情景:
最近為一個年代久遠的庫重寫了typescript版本,(這個庫能把html文本轉化為docx文檔)活合,里面遇到最主要的就是options的處理,也就是函數(shù)參數(shù)選項處理胧谈,情景如下:
const data = await asBlob(htmlString, { orientation: 'landscape', margins: { top: 100 } })
這里的函數(shù)入口的主選項允許只提供部分參數(shù),而且嵌套子選項的margins也需要允許只提供部分參數(shù)荸频。
剛開始我使用函數(shù)的可選和默認參數(shù)進行設計菱肖,可是效果不理想,想不出如何推導類型旭从。
處理方案:
后來我總結出了一套用Partial
和mergeOptions
合并選項稳强,并且聲明子選項默認值,用默認值構造子選項類型的方法和悦。實現(xiàn)比較簡單和優(yōu)雅退疫,當然如果你有更好的解決方案,歡迎提出評論意見鸽素!
首先褒繁,我個人傾向于在最里層的函數(shù),也就是使用到這個參數(shù)的函數(shù)那里定義所需的數(shù)據(jù)類型馍忽。
比如margins是在 src/template/documentTemplates.ts
這里用到的棒坏,我們就在這個文件這里聲明Margins類型或者接口。
同時遭笋,我建議選項類型坝冕,先定義默認參數(shù),再用typeof
進行初始化:
// 聲明margins的默認參數(shù)
export const defaultMargins = {
top: 1440,
right: 1440,
bottom: 1440,
left: 1440,
header: 720,
footer: 720,
gutter: 0,
}
// 通過參數(shù)聲明margins類型
export type Margins = typeof defaultMargins
// 同文件函數(shù)直接引用這個類型
export const documentTemplate = (..., margins: Margins) => {}
但是坐梯,我們希望選項在入口函數(shù)的時候我們只需要填寫部分參數(shù)即可徽诲,比如上面的margins,我們可能只是想要指定top參數(shù)吵血,這該怎么處理呢谎替?
這里我建議在入口函數(shù)的相關參數(shù)處使用Partial
部分類型。Partial
是typescript全局提供的工具類型蹋辅,你只需要提供一個“完整類型”钱贯,它就能生成一個“部分類型”。
function asBlob(html: string, options: Partial<DocumentOptions> = {})
它的實現(xiàn)也十分簡單:
type Partial<T> = {
[P in keyof T]?: T[P];
};
最后侦另,我們在入口函數(shù)向參數(shù)使用函數(shù)傳參處使用mergeOptions
合并參數(shù)就可以了:
function mergeOptions<T>(options: T, patch: Partial<T>) {
return { ...options, ...patch } as T
}
const marginsOptions = mergeOptions(defaultMargins, margins)
這一套方案可以消滅大多數(shù)的用嵌套對象做選項參數(shù)的情景啦秩命。
字面量自動擴展(type widen)問題
在項目中,我需要用一個字符串字面量(String Literal Types)來做參數(shù)褒傅,在正常使用的時候沒問題弃锐,比如:
function asBlob(html, {orientation: ''}) //編輯器會自動補全,并且類型正確推導
但是殿托,當把選項聲明為獨立的Object
霹菊,再放進去的時候會提示TypeError
,顯示oritentation應該是Orient類型支竹,不應該是string類型旋廷。
import { asBlob } from 'html-docx-js-typescript'
const opt = {
margin: {
top: 100
},
orientation: 'landscape' // type error: because typescript automatically widen this type to 'string' but not 'Orient' - 'string literal type'
}
await asBlob(html, opt)
這是怎么回事鸠按?我明明聲明的時候用的是字面量中的其中一個啊饶碘!
后來通過typescript issueSyntax for hinting literal type inference #10195了解到目尖,這是字面量參數(shù)自動發(fā)生了類型擴展(type widening),因為你聲明一個獨立對象的時候扎运,你不能保證這個字面量參數(shù)不會發(fā)生變化瑟曲,因此typescript自動把它擴展為string
類型,導致類型錯誤绪囱!
這個時候我們應該讓它避免類型擴展测蹲,你可以暴露Orient 類型,用類型斷言來指定這個類型鬼吵,也可以用const
把類型固定為常量扣甲。因為我不想暴露這個類型,讓用戶產(chǎn)生心智負擔齿椅,所以我推薦直接用as const
琉挖。
const opt = {
margin: {
top: 100
},
orientation: 'landscape' as const
}
??如果有大佬提出更好的解決方案,可以評論區(qū)交流哈~