Item 37: Limit the Use of Optional Properties
May 10, 2024 ยท View on GitHub
Things to Remember
- 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.
Code Samples
interface FormattedValue {
value: number;
units: string;
}
function formatValue(value: FormattedValue) { /* ... */ }
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}`;
}
type UnitSystem = 'metric' | 'imperial';
interface FormattedValue {
value: number;
units: string;
/** default is imperial */
unitSystem?: UnitSystem;
}
interface AppConfig {
darkMode: boolean;
// ... other settings ...
/** default is imperial */
unitSystem?: UnitSystem;
}
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}`;
}
declare let config: AppConfig;
const unitSystem = config.unitSystem ?? 'imperial';
const unitSystem = config.unitSystem ?? 'metric';
interface InputAppConfig {
darkMode: boolean;
// ... other settings ...
/** default is imperial */
unitSystem?: UnitSystem;
}
interface AppConfig extends InputAppConfig {
unitSystem: UnitSystem; // required
}
function normalizeAppConfig(inputConfig: InputAppConfig): AppConfig {
return {
...inputConfig,
unitSystem: inputConfig.unitSystem ?? 'imperial',
};
}