Item 37: Limit the Use of Optional Properties
要点
- Optional properties can prevent the type checker from finding bugs and can lead to repeated and possibly inconsistent code for filling in default values.
- Think twice before adding an optional property to an interface. Consider whether you could make it required instead.
- Consider creating distinct types for un-normalized input data and normalized data for use in your code.
- Avoid a combinatorial explosion of options.
- 可选属性可能会阻止类型检查器发现错误,并可能导致重复且可能不一致的代码来填充默认值。
- 在向接口添加可选属性之前,三思而后行。考虑是否可以将其改为必填属性。
- 考虑为未标准化的输入数据和标准化的数据创建不同的类型,以便在代码中使用。
- 避免选项的组合爆炸。
正文
ts
interface FormattedValue {
value: number
units: string
}
function formatValue(value: FormattedValue) {
/* ... */
}
ts
interface Hike {
miles: number
hours: number
}
function formatHike({ miles, hours }: Hike) {
const distanceDisplay = formatValue({ value: miles, units: 'miles' })
const paceDisplay = formatValue({ value: miles / hours, units: 'mph' })
return `${distanceDisplay} at ${paceDisplay}`
}
ts
type UnitSystem = 'metric' | 'imperial'
interface FormattedValue {
value: number
units: string
/** default is imperial */
unitSystem?: UnitSystem
}
ts
interface AppConfig {
darkMode: boolean
// ... other settings ...
/** default is imperial */
unitSystem?: UnitSystem
}
ts
function formatHike({ miles, hours }: Hike, config: AppConfig) {
const { unitSystem } = config
const distanceDisplay = formatValue({
value: miles,
units: 'miles',
unitSystem,
})
const paceDisplay = formatValue({
value: miles / hours,
units: 'mph', // forgot unitSystem, oops!
})
return `${distanceDisplay} at ${paceDisplay}`
}
ts
declare let config: AppConfig
const unitSystem = config.unitSystem ?? 'imperial'
ts
const unitSystem = config.unitSystem ?? 'metric'
ts
interface InputAppConfig {
darkMode: boolean
// ... other settings ...
/** default is imperial */
unitSystem?: UnitSystem
}
interface AppConfig extends InputAppConfig {
unitSystem: UnitSystem // required
}
ts
function normalizeAppConfig(inputConfig: InputAppConfig): AppConfig {
return {
...inputConfig,
unitSystem: inputConfig.unitSystem ?? 'imperial',
}
}