A type alias is a way to give a new name to an existing type. It can represent primitive types, union types, intersection types, or even more complex types. Here is a simple example of a type alias for a string representing an email:
// Type alias for an email string
type Email = string;
function sendEmail(to: Email) {
console.log(`Sending email to ${to}`);
}
const userEmail: Email = "[email protected]";
sendEmail(userEmail);
In this example, the Email
type alias makes it clear that the to
parameter in the sendEmail
function should be an email address, improving the code’s readability.
Interfaces are used to define the structure of an object. They can describe the properties and their types, as well as methods that an object should have.
// Interface for a person object
interface Person {
name: string;
age: number;
greet(): void;
}
const person: Person = {
name: "John",
age: 30,
greet() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
}
};
person.greet();
The Person
interface clearly defines what properties and methods a Person
object should have, making the code more understandable.
Enums are used to define a set of named constants. They can make the code more readable by using meaningful names instead of raw values.
// Enum for days of the week
enum DaysOfWeek {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
}
function getDayName(day: DaysOfWeek) {
return DaysOfWeek[day];
}
const today = DaysOfWeek.Monday;
console.log(getDayName(today));
Here, the DaysOfWeek
enum gives meaningful names to the days of the week, making the code easier to read and understand.
Custom types can be combined using union and intersection types.
A union type allows a variable to have one of several types.
// Union type for a value that can be either a string or a number
type StringOrNumber = string | number;
function printValue(value: StringOrNumber) {
if (typeof value === "string") {
console.log(`The value is a string: ${value}`);
} else {
console.log(`The value is a number: ${value}`);
}
}
printValue("Hello");
printValue(123);
The StringOrNumber
union type clearly indicates that the value
parameter in the printValue
function can be either a string or a number.
An intersection type combines multiple types into one. An object of an intersection type must satisfy all the types in the intersection.
// Intersection type for a person who is also an employee
interface Person {
name: string;
age: number;
}
interface Employee {
employeeId: number;
department: string;
}
type EmployeePerson = Person & Employee;
const employeePerson: EmployeePerson = {
name: "Jane",
age: 25,
employeeId: 12345,
department: "HR"
};
console.log(`Name: ${employeePerson.name}, Employee ID: ${employeePerson.employeeId}`);
The EmployeePerson
intersection type combines the properties of Person
and Employee
, clearly defining the structure of an object that represents both a person and an employee.
Custom types can be used in function signatures to clearly define the input and output types.
// Custom type for a user object
interface User {
id: number;
username: string;
email: string;
}
// Function that takes a user object and returns a formatted string
function formatUser(user: User): string {
return `ID: ${user.id}, Username: ${user.username}, Email: ${user.email}`;
}
const user: User = {
id: 1,
username: "testuser",
email: "[email protected]"
};
console.log(formatUser(user));
Using the User
interface in the function signature makes it clear what kind of object the formatUser
function expects and what it returns.
When working with APIs, custom types can be used to define the structure of the API responses.
// Custom type for an API response
interface ApiResponse {
status: number;
message: string;
data: any;
}
function handleApiResponse(response: ApiResponse) {
if (response.status === 200) {
console.log(`Success: ${response.message}`);
} else {
console.log(`Error: ${response.message}`);
}
}
const apiResponse: ApiResponse = {
status: 200,
message: "Data fetched successfully",
data: { key: "value" }
};
handleApiResponse(apiResponse);
The ApiResponse
interface clearly defines the structure of the API response, making it easier to handle the response in the handleApiResponse
function.
Custom types should be as simple as possible and represent a single concept. Avoid creating overly complex types that combine too many unrelated properties.
Choose descriptive names for custom types. For example, instead of using a generic name like Data
, use a more specific name like UserProfileData
if the type represents user profile information.
Use JSDoc comments to document custom types, especially if they are complex or used in a shared codebase.
/**
* Represents a book object.
* @property {number} id - The unique identifier of the book.
* @property {string} title - The title of the book.
* @property {string} author - The author of the book.
*/
interface Book {
id: number;
title: string;
author: string;
}
Custom types in TypeScript are a powerful tool for improving code readability. By using type aliases, interfaces, enums, and combining types through unions and intersections, developers can create clear and concise code that is easy to understand and maintain. Following common practices and best practices when using custom types further enhances the quality of the codebase. Whether it’s defining function signatures, handling API responses, or representing complex data structures, custom types play a crucial role in modern TypeScript development.