Learn TypeScript: Optional Properties, Optional Chaining, & Enum

You may want to read this article in Bahasa Indonesia version: Mengenal TypeScript: Optional Properties, Optional Chaining, & Enum
TypeScript offers a powerful data type system to help develop more secure and maintainable applications. This article will discuss two important features in the TypeScript type system: Optional Properties, Optional Chaining, and Enum.
Optional Properties
In TypeScript, we can mark a property as optional by adding a question mark (?
) after the property name. This means that the property may or may not be present in an object.
/** Defining a type with optional properties */
type User = {
id: number;
name: string;
/** email is optional property */
email?: string;
/** phone is optional property */
phone?: string;
};
/** Creating an object without optional properties */
const user1: User = {
id: 1,
name: "Budi",
};
/** Creating an object with optional properties */
const user2: User = {
id: 2,
name: "Ani",
email: "ani@example.com",
phone: "08123456789",
};
Benefits of Optional Properties
Some of the benefits of optional properties include
Flexibility
Allows us to create flexible data structures without losing type safety.
Validation
TypeScript will validate that the required property is always present.
Documentation
Code becomes more expressive and self-documenting.
Optional Chaining
Optional chaining is a feature that allows us to access properties or call methods from objects that may be null
or undefined
without causing an error. This feature is represented with the ?
operator.
type Address = {
street: string;
city: string;
postalCode?: string;
};
type User = {
id: number;
name: string;
address?: Address;
};
function getPostalCode(user: User): string | undefined {
// Without optional chaining
// if (user.address && user.address.postalCode) {
// return user.address.postalCode;
// }
// return undefined;
// With optional chaining
return user.address?.postalCode;
}
const user1: User = {
id: 1,
name: "Dian",
};
const user2: User = {
id: 2,
name: "Eko",
address: {
street: "Jl. Merdeka",
city: "Jakarta",
},
};
const user3: User = {
id: 3,
name: "Fira",
address: {
street: "Jl. Sudirman",
city: "Jakarta",
postalCode: "12345",
},
};
console.log(getPostalCode(user1)); // undefined
console.log(getPostalCode(user2)); // undefined
console.log(getPostalCode(user3)); // "12345"
Optional Chaining with Methods
Optional chaining can also be used to call methods that may not exist.
type Calculator = {
add: (a: number, b: number) => number;
subtract?: (a: number, b: number) => number;
};
const basicCalc: Calculator = {
add: (a, b) => a + b,
};
const fullCalc: Calculator = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
};
// Calling method that may not exist
console.log(basicCalc.subtract?.(5, 3)); // undefined
console.log(fullCalc.subtract?.(5, 3)); // 2
Nulllish Coalescing Operator
Optional chaining is often used in conjunction with the nulllish coalescing operator (??
) to provide a default value when the result is null
or undefined
.
function getCity(user: User): string {
return user.address?.city ?? "Unknown city";
}
console.log(getCity(user1)); // "Unknown city"
console.log(getCity(user2)); // "Jakarta"
Enum
Enum (enumeration) is a way to define a group of named constants. Enums allow us to document intent or create a collection of different cases.
/** Defining enum */
enum Direction {
Up,
Down,
Left,
Right,
}
/** Using enum */
function move(direction: Direction): void {
switch (direction) {
case Direction.Up:
console.log("Moving up");
break;
case Direction.Down:
console.log("Moving down");
break;
case Direction.Left:
console.log("Moving left");
break;
case Direction.Right:
console.log("Moving right");
break;
}
}
move(Direction.Up); // "Moving up"
move(Direction.Right); // "Moving right"
Enums with Explicit Values
By default, an enum will provide a numeric value starting from 0. We can also provide explicit values for each enum member.
/** Enum with string values */
enum ApiStatus {
Success = "SUCCESS",
Error = "ERROR",
Loading = "LOADING",
}
function handleApiResponse(status: ApiStatus): void {
if (status === ApiStatus.Success) {
console.log("API success");
} else if (status === ApiStatus.Error) {
console.log("API error");
} else {
console.log("API loading");
}
}
handleApiResponse(ApiStatus.Success); // "API success"
Const Enum
For performance optimization, TypeScript provides const enum
which will be completely removed at compile time and replaced with its literal value.
/** Const enum */
const enum HttpStatus {
OK = 200,
Created = 201,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404,
InternalServerError = 500,
}
function handleHttpResponse(status: number): void {
if (status === HttpStatus.OK) {
console.log("Request success");
} else if (status === HttpStatus.NotFound) {
console.log("Resource not found");
} else if (status >= HttpStatus.BadRequest) {
console.log("Terjadi error");
}
}
handleHttpResponse(HttpStatus.OK); // "Request success"
handleHttpResponse(HttpStatus.NotFound); // "Resource not found"
Enum vs Union Type
In some cases, union types of literal values can be a better alternative to enums, especially if we want a more functional approach.
/** Using enum */
enum Theme {
Light,
Dark,
System,
}
/** Using union type */
type ThemeType = "light" | "dark" | "system";
/** Function with enum */
function setThemeEnum(theme: Theme): void {
// implementation
}
/** Function with union type */
function setThemeUnion(theme: ThemeType): void {
// implementation
}
// Usage
setThemeEnum(Theme.Dark);
setThemeUnion("dark");
Union types provide the same type-safety as enums, but with a simpler syntax and without the need to define additional types.
Conclusion
Optional properties and optional chaining provide flexibility in handling data that may be incomplete, while enums provide a way to define a group of related constants. Both of these features are important parts of the TypeScript type system that help us write more secure, expressive, and maintainable code.
By understanding and utilizing these features, we can develop more robust TypeScript applications and avoid many of the bugs that typically arise in regular JavaScript development.