TypeScript Tips for Better Code
TypeScript Tips for Better Code
TypeScript has become an essential tool in modern web development. Here are some practical tips that have helped me write better, more maintainable code.
1. Use Type Inference Wisely
TypeScript is smart about inferring types. Don't over-annotate when the type is obvious:
// ❌ Redundant type annotation
const count: number = 5;
// ✅ Type is inferred
const count = 5;
However, do annotate function return types for better error messages:
// ✅ Explicit return type
function getUser(id: string): User | null {
return users.find(u => u.id === id) ?? null;
}
2. Leverage Union Types
Union types make your code more precise:
type Status = 'pending' | 'approved' | 'rejected';
function updateStatus(status: Status) {
// TypeScript ensures only valid statuses can be passed
}
3. Use unknown Instead of any
When you don't know a type, use unknown instead of any:
// ❌ Dangerous - no type checking
function process(data: any) {
return data.value;
}
// ✅ Safe - requires type checking
function process(data: unknown) {
if (typeof data === 'object' && data !== null && 'value' in data) {
return data.value;
}
}
4. Create Utility Types
Reusable utility types keep your code DRY:
type ApiResponse<T> = {
data: T;
error: string | null;
loading: boolean;
};
type UserResponse = ApiResponse<User>;
type PostsResponse = ApiResponse<Post[]>;
5. Use as const for Literal Types
Make objects and arrays deeply readonly with as const:
const routes = {
home: '/',
about: '/about',
contact: '/contact',
} as const;
// routes.home is type '/' not string
6. Discriminated Unions for State
Model complex state with discriminated unions:
type DataState<T> =
| { status: 'loading' }
| { status: 'error'; error: string }
| { status: 'success'; data: T };
function render<T>(state: DataState<T>) {
switch (state.status) {
case 'loading':
return 'Loading...';
case 'error':
return state.error;
case 'success':
return state.data;
}
}
7. Avoid Enums, Use Object Literals
Instead of enums, use object literals with as const:
// ❌ Enums have runtime overhead
enum Direction {
Up,
Down,
}
// ✅ Zero runtime cost
const Direction = {
Up: 'UP',
Down: 'DOWN',
} as const;
type Direction = typeof Direction[keyof typeof Direction];
8. Generic Constraints
Use constraints to make generics more specific:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: 'Eric', age: 30 };
const name = getProperty(user, 'name'); // ✅ Works
// const invalid = getProperty(user, 'invalid'); // ❌ Error
Conclusion
These tips have helped me write more robust TypeScript code. The key is to leverage TypeScript's type system to catch errors early and make your code self-documenting.
Remember: TypeScript is a tool to help you, not fight against. Use it to make your development experience better!