- Use the spread operator with type inference:
export function cache<T extends (...args: any[]) => any, KeyType, ResultType, ErrorType>(
fn: T,
options: Partial<ICacheOptions<KeyType, ResultType, ErrorType>> = {}
): T {
const mergedOptions: ICacheOptions<KeyType, ResultType, ErrorType> = {
...DEFAULT_CACHE_OPTIONS,
...options
};
// Rest of the function implementation...
}
This approach uses the spread operator to merge the default options with the provided options. The Partial<>
utility type allows for partial options to be passed.
- Use a utility function for deep merging with type safety:
import { defaults } from 'options-defaults';
export function cache<T extends (...args: any[]) => any, KeyType, ResultType, ErrorType>(
fn: T,
options: Partial<ICacheOptions<KeyType, ResultType, ErrorType>> = {}
): T {
const mergedOptions = defaults(DEFAULT_CACHE_OPTIONS, options);
// Rest of the function implementation...
}
This approach uses the options-defaults
library, which provides a type-safe way to merge options deeply[1]. It handles nested objects and arrays correctly while maintaining type information.
- Use TypeScript's built-in utility types:
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
export function cache<T extends (...args: any[]) => any, KeyType, ResultType, ErrorType>(
fn: T,
options: DeepPartial<ICacheOptions<KeyType, ResultType, ErrorType>> = {}
): T {
const mergedOptions: ICacheOptions<KeyType, ResultType, ErrorType> = {
...DEFAULT_CACHE_OPTIONS,
...options as ICacheOptions<KeyType, ResultType, ErrorType>
};
// Rest of the function implementation...
}
This approach uses a custom DeepPartial
type to allow for partial options at any depth. It provides more flexibility for nested options.
- Use TypeScript 4.0+'s template literal types for more precise option merging:
type MergeOptions<T, U> = {
[K in keyof T | keyof U]: K extends keyof U ? U[K] : K extends keyof T ? T[K] : never;
};
export function cache<T extends (...args: any[]) => any, KeyType, ResultType, ErrorType>(
fn: T,
options: Partial<ICacheOptions<KeyType, ResultType, ErrorType>> = {}
): T {
const mergedOptions: MergeOptions<typeof DEFAULT_CACHE_OPTIONS, typeof options> = {
...DEFAULT_CACHE_OPTIONS,
...options
};
// Rest of the function implementation...
}
This approach uses advanced type manipulation to create a merged type that accurately represents the combination of default and provided options[3].
These approaches are more idiomatic in TypeScript and provide better type safety and inference compared to using Object.assign
. They allow for partial options to be passed while ensuring that all required properties are present in the final merged options object.
Citations: [1] https://github.com/radarsu/options-defaults [2] https://stackoverflow.com/questions/9602449/a-javascript-design-pattern-for-options-with-default-values [3] https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-0.html [4] https://www.typescriptlang.org/docs/handbook/advanced-types.html [5] protobufjs/protobuf.js#1572