In this tutorial, we'll learn how to create custom types in TypeScript.
TypeScript, a typed superset of JavaScript, introduces features that improve code quality, maintainability, and scalability. One of the most powerful aspects of TypeScript is its type system, which allows you to create custom types tailored to your specific application needs. Custom types help enforce structure, prevent errors, and make code more readable.
We’ll delve deep into creating custom types in TypeScript, learn how to create arrays with a specific number of elements or more, explore custom type syntax, and understand how to compose complex types.
Understanding Custom Types in TypeScript
Custom types in TypeScript are user-defined types that help specify the structure and behavior of data in your application. These types enhance code clarity and ensure that data adheres to a predefined format.
Creating Custom Types
Custom types in TypeScript can be created using type aliases or interfaces. Both allow you to define the structure of an object, but they have subtle differences.
Using Type Aliases
A type alias assigns a name to a specific type. It can represent primitive types, objects, functions, or even other types.
// Primitive type alias
type Username = string;
// Object type alias
type User = {
id: number;
username: Username;
email: string;
isActive: boolean;
};
// Function type alias
type Callback = (data: string) => void;
Using Interfaces
An interface is specifically used to define the shape of an object. Interfaces are extensible and can be merged, making them ideal for object-oriented programming.
interface User {
id: number;
username: string;
email: string;
isActive: boolean;
}
// Extending an interface
interface Admin extends User {
adminLevel: number;
}
Key Difference:
- Use type for primitives, unions, or tuples.
- Use interface for defining objects that may need to be extended.
Creating Arrays with a Specific Number of Elements or More
TypeScript allows you to enforce constraints on arrays, such as ensuring they contain a specific number of elements or more.
Tuple Arrays with Fixed Sizes
Tuples in TypeScript are arrays with fixed types and lengths. This is useful when you know the exact number of elements and their types.
type FixedTuple = [string, number, boolean];
const example: FixedTuple = ["Hello", 42, true]; // Valid
// const invalid: FixedTuple = ["Hello", 42]; // Error: Missing element
Using Rest Elements for "At Least" Constraints
To allow arrays with a minimum number of elements but additional ones, use the spread operator (...).
type AtLeastThreeNumbers = [number, number, number, ...number[]];
const validArray: AtLeastThreeNumbers = [1, 2, 3, 4, 5]; // Valid
const anotherValidArray: AtLeastThreeNumbers = [10, 20, 30]; // Valid
// const invalidArray: AtLeastThreeNumbers = [1, 2]; // Error: Too few elements
Combining Tuple and Array Types
You can create types that are partially a tuple and partially an open-ended array.
type MixedArray = [string, ...boolean[]];
const validMixedArray: MixedArray = ["start", true, false, true];
Custom Type Syntax
TypeScript provides flexible syntax for defining custom types, allowing you to express complex relationships.
Union Types
Union types define variables that can hold multiple types.
type Status = "success" | "error" | "loading";
const apiResponse: Status = "success"; // Valid
// const invalidResponse: Status = "completed"; // Error: Invalid value
Intersection Types
Intersection types combine multiple types into one.
type User = {
id: number;
username: string;
};
type Admin = {
adminLevel: number;
};
type SuperAdmin = User & Admin;
const superAdmin: SuperAdmin = {
id: 1,
username: "adminUser",
adminLevel: 5,
};
Optional Properties
You can mark properties as optional using the ? syntax.
type Config = {
url: string;
timeout?: number; // Optional
};
const serverConfig: Config = { url: "https://example.com" };
Readonly Properties
Use the readonly modifier to ensure properties cannot be modified after initialization.
type Point = {
readonly x: number;
readonly y: number;
};
const p: Point = { x: 10, y: 20 };
// p.x = 15; // Error: Cannot assign to 'x' because it is a read-only property
Composing Types
TypeScript allows you to compose types in powerful ways, enabling you to define complex relationships and constraints.
Mapped Types
Mapped types allow you to create new types by transforming existing ones.
type User = {
id: number;
username: string;
email: string;
};
// Make all properties optional
type PartialUser = {
[Key in keyof User]?: User[Key];
};
// Readonly mapped type
type ReadonlyUser = {
readonly [Key in keyof User]: User[Key];
};
Conditional Types
Conditional types enable creating types based on logic.
type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
Template Literal Types
Template literal types allow defining custom string patterns.
type ID = `user_${number}`;
const userId: ID = "user_123"; // Valid
// const invalidId: ID = "user_abc"; // Error
Best Practices
- Use Type Aliases for Simplicity: Start with type for custom types unless you need the extensibility of interface.
- Leverage Readonly Properties: Use readonly for properties that should not change.
- Combine and Compose Types: Use union, intersection, and mapped types to enforce precise constraints.
- Avoid Overusing Any: Minimize the use of any to maintain the type safety benefits of TypeScript.
- Use Descriptive Names: Name custom types clearly to reflect their purpose.
Conclusion
Custom types in TypeScript empower developers to write precise, maintainable, and type-safe code. Whether you're working with tuples, enforcing constraints on arrays, or composing types, TypeScript's robust type system ensures clarity and reduces runtime errors. By mastering these concepts, you can take full advantage of TypeScript’s capabilities, making your applications more reliable and easier to understand.
Checkout our instant dedicated servers and Instant KVM VPS plans.