TypeScript's conditional types offer a powerful mechanism for defining types that vary based on conditions. This chapter delves into the world of conditional types, exploring their fundamentals, advanced usages, and best practices for creating dynamic and type-safe code in your TypeScript projects.
Imagine you have a function that can return different types depending on the input it receives. Conditional types allow you to define the return type based on a condition within the function signature.
function getValue(key: string): string | number {
if (key === "name") {
return "Alice";
} else {
return 30;
}
}
const name = getValue("name"); // Type is inferred as string
console.log(name); // Output: "Alice"
const age = getValue("age"); // Type is inferred as number
console.log(age); // Output: 30
The core concept of conditional types relies on the extends
keyword to check if a type extends another type. This allows you to define different return types based on the outcome of the condition.
type UserID = string;
type UserObject = { name: string; age: number };
function getUserData(id: string): UserID | UserObject {
if (typeof id === "string") {
return id; // Return UserID if id is a string
} else {
return { name: "John", age: 25 }; // Return UserObject otherwise
}
}
const userId: UserID = getUserData("user123"); // Type is UserID
const userData: UserObject = getUserData({ id: "unknown" }); // Type is UserObject
Conditional types can be combined with generics to create even more flexible type definitions that adapt based on conditions.
function getProperty(obj: T, key: K): T[K] {
return obj[key]; // Access property based on generic key type K
}
const user: { name: string; age: number } = { name: "Alice", age: 30 };
const userName = getProperty(user, "name"); // Type is string (inferred from user)
const userAge = getProperty(user, "age"); // Type is number (inferred from user)
// Error: getProperty cannot access non-existent properties
// const userEmail = getProperty(user, "email");
Conditional types can be used to refine types based on conditions. For example, ensuring a string is not empty before using it in a specific context.
type NonEmptyString = string extends "" ? never : string; // Never for empty string, string otherwise
function printNonNullString(message: NonEmptyString) {
console.log(message);
}
printNonNullString("Hello World!"); // Valid
// printNonNullString(""); // Error: NonEmptyString disallows empty string
Conditional types are ideal for functions that return different types based on their input or internal logic. This improves type safety and code clarity by explicitly defining the expected types for different scenarios.
Conditional types can be used to refine type properties based on conditions. This helps ensure type safety by restricting the allowed values or types for specific properties.
Here’s an example of a utility type using distributive conditional types to make all properties in an object optional.
type MakeOptional = {
[P in keyof T]: T[P] extends undefined ? T[P] : Optional; // Recursive for nested types
};
interface User {
name: string;
age: number;
isActive?: boolean; // Already optional
}
type OptionalUser = MakeOptional;
const user1: User = { name: "Alice", age: 30, isActive: true };
const user2: OptionalUser = { name: "Bob" }; // Only name provided, valid
// OptionalUser allows omitting some properties without errors
Distributive conditional types can be used to apply conditions to each element of a union type.
type StringLiteralUnion = "success" | "failure" | "pending";
type Message = T extends "success"
? { message: string }
: { error: string };
const successMessage: Message<"success"> = { message: "Operation successful!" };
const failureMessage: Message<"failure"> = { error: "An error occurred." };
// Error: "pending" type requires either message or error property
// const pendingMessage: Message<"pending"> = {};
Conditional types, especially with distributive conditional types, offer a powerful tool for creating dynamic and expressive type definitions in TypeScript. By understanding their potential and limitations, you can write more flexible, type-safe, and maintainable code for various scenarios. Happy coding !❤️