TypeScript has become an indispensable tool in modern JavaScript development. In this article, we'll explore the best ways to write type-safe code with TypeScript.
Advantages of TypeScript
1. Early Error Detection
TypeScript catches errors at compile-time. This prevents runtime errors and speeds up the development process.
2. Better IDE Support
With TypeScript, IDEs provide better autocomplete and refactoring support.
3. Code Documentation
Types document the code itself and make it easier for new developers to understand the project.
Type Definitions
Interface vs Type
// Interface - extensible
interface User {
id: string;
name: string;
}
interface Admin extends User {
role: 'admin';
}
// Type - more suitable for union and intersection
type Status = 'pending' | 'approved' | 'rejected';
type UserWithStatus = User & { status: Status };
Generic Types
Generics allow you to write reusable and type-safe code:
function getValue<T>(key: string): T | null {
const value = localStorage.getItem(key);
return value ? JSON.parse(value) : null;
}
const user = getValue<User>('user'); // Type: User | null
Utility Types
TypeScript's built-in utility types:
interface User {
id: string;
name: string;
email: string;
age: number;
}
// Partial - makes all properties optional
type PartialUser = Partial<User>;
// Pick - selects specific properties
type UserPreview = Pick<User, 'id' | 'name'>;
// Omit - removes specific properties
type UserWithoutEmail = Omit<User, 'email'>;
// Record - key-value mapping
type UserMap = Record<string, User>;
Type Guards
Type guards allow you to perform type checks at runtime:
function isUser(obj: unknown): obj is User {
return (
typeof obj === 'object' &&
obj !== null &&
'id' in obj &&
'name' in obj &&
'email' in obj
);
}
function processUser(data: unknown) {
if (isUser(data)) {
// TypeScript now knows data is User
console.log(data.name);
}
}
Strict Mode
Enable strict mode in tsconfig.json:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true
}
}
Best Practices
1. Explicit Return Types
Use explicit return types in functions:
function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
2. Avoid Any
Avoid using any. Instead, use unknown:
function processData(data: unknown) {
if (typeof data === 'string') {
// Type check with type guard
console.log(data.toUpperCase());
}
}
3. Use Const Assertions
Create more specific types with const assertions:
const colors = ['red', 'green', 'blue'] as const;
type Color = typeof colors[number]; // 'red' | 'green' | 'blue'
Conclusion
TypeScript enables you to write safer and more maintainable code. By applying these best practices, you can develop type-safe and scalable applications.