In vanilla JavaScript, a reasonable way to handle a function that might otherwise have many parameters is to instead give it a single object parameter, with the original parameters as properties. This is especially nice when the properties have sensible defaults.
function foo({a = 1, b = 2, c = 3} = {}) {
return { a, b, c };
}
// we can call it like this
foo({a: 7}); // => { a: 7, b: 2, c: 3 }
foo({b: 0, c: 0 }); // => { a: 1, b: 0, c: 0 }
foo(); // => { a: 1, b: 2, c: 3 }
Each property in the object has a default, and if no object at all is passed, an empty object is used instead, which has each of its properties replaced by the default ones. Cool, right?
What are we to do in TypeScript? We probably have a type for the thing going into the function:
type Thing = {
a: number;
b: number;
c: number;
};
function foo({a, b, c }: Thing): Thing {
return { a, b, c };
}
But then how do we handle default arguments? Any missing properties will mean our object is not a Thing
. We could make all the properties optional on Thing
, but it feels kinda gross. If a thing is not a valid Thing
without all the properties, we don’t want any of those properties to be optional. A few months ago, I did not know how to solve this neatly, so I spent a while futzing with options.
The moral of this story is that Partial
exists. A Partial<Thing>
is a Thing
where every property is optional. Because Partial
is a type, we can inline it in the annotation without modifying our Thing
type definition at all. We know our function will be dealing with a valid Thing
, because we provide a default Thing
.
type Thing = {
a: number;
b: number;
c: number;
};
function foo({ a = 1, b = 2, c = 3 }: Partial<Thing> = {}): Thing {
return { a, b, c };
}
// and the calls work the same
foo({ a: 7 }); // => { a: 7, b: 2, c: 3 }
foo({ b: 0, c: 0 }); // => { a: 1, b: 0, c: 0 }
foo(); // => { a: 1, b: 2, c: 3 }
This feels like the perfect use for Partial
. It is nice that TypeScript is incrementally adoptable, and starts providing benefits as soon as a few judicious number
and string
annotations are added. But it’s really nice that the more I lean into the type system, the better it gets.