TypeScript Best Practices for Modern Development
TypeScript Best Practices for Modern Development
TypeScript has become the de facto standard for building large-scale JavaScript applications. Here are some best practices to help you leverage TypeScript effectively.
Use Strict Mode
Always enable strict mode in your tsconfig.json:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}
This catches more potential errors at compile time and enforces better coding practices.
Prefer Interfaces Over Type Aliases for Objects
While both work, interfaces are generally preferred for object shapes:
// Good
interface User {
id: string;
name: string;
email: string;
}
// Also fine, but interfaces are more extensible
type User = {
id: string;
name: string;
email: string;
}
Use Union Types Instead of Enums
Union types are more flexible and tree-shakeable:
// Instead of enum
type Status = 'pending' | 'approved' | 'rejected';
// You can still get autocomplete and type safety
function handleStatus(status: Status) {
// TypeScript knows the exact values
}
Leverage Type Guards
Type guards help TypeScript narrow down types:
function isUser(obj: unknown): obj is User {
return (
typeof obj === 'object' &&
obj !== null &&
'id' in obj &&
'name' in obj
);
}
if (isUser(data)) {
// TypeScript knows data is User here
console.log(data.name);
}
Use Utility Types
TypeScript provides helpful utility types:
// Make all properties optional
type PartialUser = Partial<User>;
// Pick specific properties
type UserPreview = Pick<User, 'id' | 'name'>;
// Make properties readonly
type ImmutableUser = Readonly<User>;
// Exclude properties
type UserWithoutEmail = Omit<User, 'email'>;
Avoid any at All Costs
Use unknown instead of any when you don't know the type:
// Bad
function processData(data: any) {
return data.value; // No type checking
}
// Good
function processData(data: unknown) {
if (typeof data === 'object' && data !== null && 'value' in data) {
return data.value;
}
throw new Error('Invalid data');
}
Use Discriminated Unions
Discriminated unions are powerful for modeling complex state:
type LoadingState = { status: 'loading' };
type SuccessState = { status: 'success'; data: User };
type ErrorState = { status: 'error'; error: string };
type AsyncState = LoadingState | SuccessState | ErrorState;
function renderState(state: AsyncState) {
switch (state.status) {
case 'loading':
return 'Loading...';
case 'success':
return state.data.name; // TypeScript knows data exists
case 'error':
return state.error; // TypeScript knows error exists
}
}
Conclusion
These best practices will help you write more maintainable, type-safe TypeScript code. Remember, TypeScript's power comes from its type system—use it to your advantage!