Kembali ke Blog
TypeScript

Praktik Terbaik TypeScript untuk Proyek Skala Besar

Aditya Fakhri Riansyah
15 Feb 2023
10 min
Praktik Terbaik TypeScript untuk Proyek Skala Besar

TypeScript telah menjadi pilihan utama untuk pengembangan aplikasi JavaScript skala besar karena menawarkan type safety, tooling yang lebih baik, dan peningkatan developer experience. Namun, untuk memaksimalkan manfaat TypeScript dalam proyek berskala besar, diperlukan praktik terbaik yang tepat. Artikel ini akan membahas strategi dan tips untuk mengelola proyek TypeScript yang kompleks.

Mengapa TypeScript untuk Proyek Skala Besar?

Sebelum membahas praktik terbaik, penting untuk memahami mengapa TypeScript sangat berharga untuk proyek skala besar:

  • Type Safety: Mengurangi bug runtime dengan mendeteksi error pada compile time.
  • Refactoring yang Aman: IDE dapat memberikan refactoring tools yang reliable berkat type information.
  • Dokumentasi Built-in: Type definitions berfungsi sebagai dokumentasi yang selalu up-to-date.
  • Developer Experience: Autocomplete dan intellisense yang lebih baik meningkatkan produktivitas.
  • Skalabilitas: Memudahkan onboarding developer baru dan maintenance jangka panjang.

Struktur Proyek dan Organisasi

1. Struktur Folder yang Konsisten

Organisasi kode yang baik sangat penting untuk proyek berskala besar:

  • Feature-based Structure: Organisasi berdasarkan fitur daripada jenis file.
  • Modularisasi: Pecah aplikasi menjadi modul yang dapat dikelola secara independen.
  • Barrel Files: Gunakan file index.ts untuk mengekspor dari modul.

Contoh struktur folder:

src/
├── features/
│   ├── auth/
│   │   ├── components/
│   │   ├── services/
│   │   ├── hooks/
│   │   ├── types.ts
│   │   └── index.ts
│   ├── products/
│   │   ├── components/
│   │   ├── services/
│   │   ├── hooks/
│   │   ├── types.ts
│   │   └── index.ts
├── shared/
│   ├── components/
│   ├── hooks/
│   ├── utils/
│   └── types/
├── core/
│   ├── api/
│   ├── config/
│   └── store/
└── app.tsx
      

2. Path Aliases

Gunakan path aliases untuk menghindari import relatif yang panjang:

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@features/*": ["src/features/*"],
      "@shared/*": ["src/shared/*"],
      "@core/*": ["src/core/*"]
    }
  }
}
      

Dengan konfigurasi ini, Anda dapat mengimpor seperti ini:

import { UserProfile } from '@features/auth/components';
import { Button } from '@shared/components';
import { api } from '@core/api';
      

Type System Best Practices

1. Strict Mode

Aktifkan strict mode di tsconfig.json untuk mendapatkan manfaat maksimal dari type checking:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true
  }
}
      

2. Type vs Interface

Gunakan keduanya secara konsisten:

  • Interface: Untuk definisi objek yang dapat diperluas dan API publik.
  • Type: Untuk union types, intersection types, dan tipe yang tidak perlu diperluas.
// Interface untuk objek yang dapat diperluas
interface User {
  id: string;
  name: string;
  email: string;
}

// Extending interface
interface AdminUser extends User {
  permissions: string[];
}

// Type untuk union dan intersection
type UserRole = 'admin' | 'editor' | 'viewer';
type UserWithRole = User & { role: UserRole };
      

3. Utility Types

Manfaatkan utility types bawaan TypeScript untuk transformasi tipe:

// Partial untuk optional fields
type UserUpdate = Partial;

// Pick untuk subset fields
type UserCredentials = Pick;

// Omit untuk mengecualikan fields
type PublicUser = Omit;

// Record untuk map structure
type UserRoles = Record;
      

4. Type Guards

Gunakan type guards untuk narrowing types:

// User-defined type guard
function isAdminUser(user: User): user is AdminUser {
  return 'permissions' in user;
}

// Usage
function handleUser(user: User) {
  if (isAdminUser(user)) {
    // TypeScript knows user is AdminUser here
    console.log(user.permissions);
  }
}
      

State Management

1. Typed State

Definisikan tipe untuk state management dengan jelas:

// Redux with TypeScript
interface AppState {
  auth: {
    user: User | null;
    isLoading: boolean;
    error: string | null;
  };
  products: {
    items: Product[];
    selectedProduct: Product | null;
  };
}

// Action types
type AuthAction = 
  | { type: 'LOGIN_REQUEST' }
  | { type: 'LOGIN_SUCCESS', payload: User }
  | { type: 'LOGIN_FAILURE', payload: string };
      

2. Immutability

Gunakan immutable patterns dan helper libraries:

// Bad: Mutating state
function updateUser(user: User, name: string) {
  user.name = name; // Mutation!
  return user;
}

// Good: Creating new object
function updateUser(user: User, name: string): User {
  return { ...user, name }; // Immutable update
}

// With immer
import produce from 'immer';

function updateUser(user: User, name: string): User {
  return produce(user, draft => {
    draft.name = name;
  });
}
      

API dan Asynchronous Code

1. Typed API Responses

Definisikan tipe untuk API responses:

interface ApiResponse {
  data: T;
  status: number;
  message: string;
}

async function fetchUsers(): Promise> {
  const response = await api.get('/users');
  return response.data;
}
      

2. Error Handling

Tipe error dengan baik untuk error handling yang lebih robust:

interface ApiError {
  status: number;
  message: string;
  code: string;
}

async function fetchData(url: string): Promise {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      const error: ApiError = await response.json();
      throw error;
    }
    return await response.json();
  } catch (error) {
    // Handle and transform error
    if (error instanceof Error) {
      throw new Error(`API Error: ${error.message}`);
    }
    throw error;
  }
}
      

Testing

1. Typed Mocks

Gunakan tipe untuk mocks dalam testing:

// Mock user service
const mockUserService: UserService = {
  getUser: jest.fn().mockResolvedValue({ id: '1', name: 'Test User' }),
  updateUser: jest.fn().mockResolvedValue(true)
};

// Type-safe assertions
expect(mockUserService.getUser).toHaveBeenCalledWith('1');
      

2. Test Utilities

Buat test utilities yang type-safe:

function renderWithProviders(
  ui: React.ReactElement,
  options?: {
    initialState?: Partial;
    store?: Store;
  }
) {
  const store = options?.store || createStore({
    ...defaultState,
    ...options?.initialState
  });
  
  return {
    ...render({ui}),
    store
  };
}
      

Performance Optimization

1. Type-Only Imports

Gunakan type-only imports untuk menghindari bloat pada bundle:

// Import only the type, not the actual implementation
import type { User } from './types';
      

2. Project References

Untuk monorepos atau proyek besar, gunakan project references:

// tsconfig.json
{
  "references": [
    { "path": "./packages/common" },
    { "path": "./packages/frontend" },
    { "path": "./packages/backend" }
  ]
}
      

Documentation

1. JSDoc Comments

Gunakan JSDoc untuk dokumentasi yang lebih kaya:

/**
 * Represents a user in the system
 * @interface User
 */
interface User {
  /** Unique identifier for the user */
  id: string;
  /** User's full name */
  name: string;
  /** User's email address */
  email: string;
}

/**
 * Fetches a user by their ID
 * @param {string} id - The user's ID
 * @returns {Promise} The user object
 * @throws {Error} If the user is not found
 */
async function getUser(id: string): Promise {
  // Implementation
}
      

2. Generate Documentation

Gunakan tools seperti TypeDoc untuk generate dokumentasi dari kode TypeScript:

// typedoc.json
{
  "entryPoints": ["src/index.ts"],
  "out": "docs",
  "excludePrivate": true,
  "excludeProtected": true,
  "theme": "default"
}
      

Kesimpulan

Mengelola proyek TypeScript skala besar memerlukan disiplin dan praktik terbaik yang konsisten. Dengan menerapkan strategi yang dibahas dalam artikel ini, Anda dapat memaksimalkan manfaat TypeScript dan membangun aplikasi yang lebih maintainable, scalable, dan robust.

Ingat bahwa TypeScript adalah alat yang powerful, tetapi juga fleksibel. Sesuaikan praktik terbaik ini dengan kebutuhan spesifik proyek Anda dan tim development. Dengan pendekatan yang tepat, TypeScript dapat menjadi fondasi yang solid untuk aplikasi kompleks Anda.

TypeScript
JavaScript
Best Practices
Architecture
Development