Who is Eric Su?

TypeScript Tips for Better Code

3 min read
Eric Su
typescriptjavascriptbest-practices

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!