Introduction
TypeScript is a powerful programming language that provides developers with the ability to specify static types for their code, catching errors at compile-time and improving code quality.
When working with TypeScript, developers often need to specify whether a property, type, or function parameter is optional or required, and whether it can have a value of undefined
. This is typically done using either optional properties or union types with undefined
as an option.
In this article, we will explore the differences between these two approaches and how they can be used to enforce required properties in interfaces, required parameters in functions, and required types in variable declarations. We will also provide practical examples to demonstrate the benefits and use cases of each approach.
Is shorter code always better?
When it comes to defining optional properties in TypeScript, developers commonly use the shorthand notation with a question mark, such as prop?: type
. However, it’s important to note that this shorthand notation does not mean the same thing as using a union type with undefined
, such as prop: type | undefined
. The former denotes an optional property, while the latter denotes a required property that can be assigned the value of undefined
. Therefore, it’s crucial to use the correct notation depending on the desired behavior. Util functions for internal usage should also be carefully considered to determine if they should be callable without parameters at all.
type OptionalProp = {
prop?: string
}
function funcOptional(propObj: OptionalProp): void {
console.log(`Optional property value is ${propObj.prop}`)
}
funcOptional({ prop: undefined }) // Output: Optional property value is undefined
funcOptional({}) // Output: Optional property value is undefined
In this example, we define a type with an optional prop
property, which can be undefined
. The funcOptional
function takes an object parameter of type OptionalProp
and outputs the value of prop
. When the function is called with an object that has prop
set to undefined or is missing, the output will be undefined
.
type UnionProp = {
prop: string | undefined
}
function funcUnion(propObj: UnionProp): void {
console.log(`Union type property value is ${propObj.prop}`)
}
funcUnion({ prop: undefined }) // Output: Union type property value is undefined
funcUnion({}) // Error: 'prop' is missing in type '{ }'
On the other hand, when we define a type with a union type that includes undefined as an option, the resulting type will require the property, but it can have a value of undefined
. In the funcUnion
function, when the prop
parameter is called with an object that has prop set to undefined
, the output will be undefined. However, if the function is called with an object that does not have the prop
property, TypeScript will throw an error.
When choosing between these two methods, it’s important to consider the use-case and whether it makes sense for the function or object to be invoked without the parameter or property at all.
Example
Let’s take a look at another code example to gain a better understanding of the difference.
type Person = {
name: string
age?: number
}
function greet(person: Person): void {
if (person.age) {
console.log(`Hi ${person.name}, you're ${person.age} years old.`)
} else {
console.log(`Hi ${person.name}, I don't know how old you are.`)
}
}
greet({ name: 'Alice', age: undefined }) // Output: Hi Alice, I don't know how old you are.
greet({ name: 'Bob' }) // Output: Hi Bob, I don't know how old you are.
In this example, the Person
type has an optional age
property, which can either be a number or undefined
. The greet
function takes a Person
parameter and outputs a message that depends on whether the age
property is present and has a value. When the function is called with a Person
object that has an age
property set to undefined
, the output message indicates that the age is unknown.
type PersonUnion = {
name: string
age: number | undefined
}
function greetUnion(person: PersonUnion): void {
if (person.age !== undefined) {
console.log(`Hi ${person.name}, you're ${person.age} years old.`)
} else {
console.log(`Hi ${person.name}, I don't know how old you are.`)
}
}
greetUnion({ name: 'Alice', age: undefined }) // Output: Hi Alice, you're undefined years old.
greetUnion({ name: 'Bob' }) // Error: 'age' is missing in type '{ name: string; }'
In the second example, the PersonUnion
type uses a union type to allow the age
property to be either a number
or undefined
. The greetUnion
function behaves similarly to greet
, but in this case, the output message when the age
property is undefined
is different. When the function is called with a PersonUnion
object that has an age
property set to undefined
, the output message indicates that the age
is undefined. However, if the function is called with a PersonUnion
object that does not have an age
property, TypeScript will throw an error indicating that the age
property is missing. This is because the age
property is not optional in the PersonUnion
type, and must be present in any object that conforms to this type.
Summary
The main takeaway is that while shorter code may be desirable, it’s crucial to use the correct notation depending on the desired behavior, and to carefully consider whether it makes sense for the function or object to be invoked without the parameter or property at all. Action points for the reader include carefully considering the use-case when choosing between optional properties or union types, and ensuring that util functions for internal usage are callable with parameters when necessary.