Hey, I'm Wyatt! I work on
at
. I'm passionate about open source and building secure, impactful software. Want to chat about better software or collaborate on a project? Find me on
or
! Any comments or questions on this please reach out
.
A popular feature in Typescript lets you mark a given property as optional in a type or interface, these are called
:
These are pretty useful in lots of circumstances as it allows you to keep the code clean and simple but still allow some flexibility when it comes to calling functions and defining objects. In React for example, we use optional boolean properties on
Props
in order to make them flexible:
This works great in these cases because the number of properties is usually pretty small. In larger codebases however where these optional properties are used as arguments for functions, things can get a little more complicated:
The problem in this case is that there’s a risk that by not defining or passing each parameter, you could have different behaviours. With the advent of the
(
?.
), code that feels natural to write can have odd consequences:
In this case, the code above “optionally” gets the value from the cache if the cache is provided. This makes sense from within the function’s scope, but outside, this tradeoff isn’t quite that clear.
Rather than relying on optional properties, you could instead still leverage the optional chaining operator by explicitly requiring that the value be specified, even if it’s not available:
This forces developers to specify the absence of the parameter rather than letting it silently get ignored, leading to the following:
Being explicit with the properties passed allows users to always see explicitly what’s being passed to the function rather then being silent. This is even compatible with the optional chaining operator as it forces users to still pass the property but it will return
undefined
if the property is not defined:
I’m definitely not advocating for
all
properties to be explicit, but for those involved with critical control flows in functions and your applications, you’re probably better off being explicit with those properties instead of leaving them optional.
1type Props = {
2 inverted?: boolean;
3 color: string;
4}
1<Button color="red" />
2<Button color="blue" inverted />
1type Context = {
2 // ... 10 other properties
3 cache?: IncrementalCache;
4}
5
6function build(ctx: Context): void
1function build(ctx: Context): void {
2 // ...
3 const value = ctx.cache?.get(key)
4 // ...
5}
1type Context = {
2 // ...
3 cache: IncrementalCache | undefined;
4}
1// With cache?: IncrementalCache
2build({ /* ... */ })
3build({ cache, /* ... */ })
4
5// With cache: IncrementalCache | undefined
6build({ /* ... */ }) // Property 'cache' is missing
7build({ cache: undefined, /* ... */ })
8build({ cache, /* ... */ })
1let ctx: { cache?: IncrementalCache } | undefined
2
3build({ cache: ctx?.cache })