Can you have optional destructured arguments in a Typescript function?
TypescriptTypescript Problem Overview
I'd like to write a function that takes an object argument, uses destructuring in the function signature, and have that argument be optional:
myFunction({opt1, opt2}?: {opt1?: boolean, opt2?: boolean})
However, Typescript doesn't let me ("A binding pattern parameter cannot be optional in an implementation signature.").
Of course I could do it if I didn't destructure:
myFunction(options?: {opt1?: boolean, opt2?: boolean}) {
const opt1 = options.opt1;
const opt2 = options.opt1;
...
It seems like these should be the same thing, yet the top example is not allowed.
I'd like to use a destructured syntax (1) because it exists, and is a nice syntax, and it seems natural that the two functions above should act the same, and (2) because I also want a concise way to specify defaults:
myFunction({opt1, opt2 = true}?: {opt1?: boolean, opt2?: boolean})
Without destructuring, I have to bury these defaults in the implementation of the function, or have an argument that is actually some class with a constructor...
Typescript Solutions
Solution 1 - Typescript
Use a default parameter instead:
function myFunction({ opt1, opt2 = true }: { opt1?: boolean; opt2?: boolean; } = {}) {
console.log(opt2);
}
myFunction(); // outputs: true
It's necessary in order to not destructure undefined
:
function myFunction({ opt1, opt2 }) {
}
// Uncaught TypeError: Cannot destructure property `opt1` of 'undefined' or 'null'.
myFunction();
Solution 2 - Typescript
You can't destructure if there is not an object given as an argument. Therefore use a default object in the parmas as the previous post mentioned:
type Options = { opt1?: boolean; opt2?: boolean; }
function myFunction({ opt1, opt2 }: Options = {}) {
console.log(opt2, opt1);
}
myFunction() // undefined, undefined
myFunction({opt1: false}); // undefined, false
myFunction({opt2: true}); // true, undefined
What I would like to add is that this destructuring pattern in the params adds the most value when the following 2 conditions hold:
- The amount of options is likely to change
- There could potentially be change in API of the function. i.e. the function params are likely to change
Basically destructuring gives you more flexibility since you can add as many options as you like with minimal change to the function's API.
However, the more basic version would be simpler:
// If the function is not likely to change often just keep it basic:
function myFunctionBasic( opt1? :boolean, opt2?: boolean ) {
console.log(opt2, opt1);
}
Solution 3 - Typescript
This is the best way if you want to mix optional and required arguments but have an exported type where all values are required / non-null:
export function newCar(args: {
year?: number
make?: string
model: string
owner: [string, string]
}) {
const defaults = {
year: 1999,
make: "toyota",
}
return { ...defaults, ...args }
// to get a Readonly<Car>:
// return Object.freeze(args)
}
export type Car = ReturnType<typeof newCar>
const c = newCar({ model: "corolla", owner: ["Gorg", "Blafson"] })
export function print(c: Car) {
console.log(`${c.owner}'s gorgeous ${c.year} ${c.model} from ${c.make}`)
}