organizing-type-definitions
Organizing Type Definitions:
Let's illustrate these key points with TypeScript examples.
- Modularity: - // mathUtils.ts
 export type AddFn = (a: number, b: number) => number;
 // Using the type in another module
 // calculator.ts
 import { AddFn } from './mathUtils';
 const add: AddFn = (a, b) => a + b;
- Declaration files: - // jquery.d.ts
 interface JQuery {
 fadeIn(): void;
 }
 // Usage in a .ts file
 /// <reference path="jquery.d.ts" />
 $('div').fadeIn();
- Namespaces (to be avoided in favor of modules): - // Instead of using namespaces
 namespace MyMath {
 export function add(a: number, b: number): number {
 return a + b;
 }
 }
 // Prefer using modules
 // math.ts
 export function add(a: number, b: number): number {
 return a + b;
 }
- Type versus interface: - // Prefer using an interface
 interface User {
 name: string;
 age: number;
 }
 // Extending the interface
 interface Admin extends User {
 privileges: string[];
 }
- Single Responsibility: - // Good: Each type has a single responsibility
 type User = {
 name: string;
 age: number;
 };
 type Permission = {
 permissions: string[];
 };
- Organize by feature: - // File structure
 /users
 userTypes.ts
 userService.ts
 /products
 productTypes.ts
 productService.ts
- Barrel exports: - // index.ts in the users folder
 export * from './userTypes';
 export * from './userService';
 // Importing from another module
 import { User, fetchUsers } from '../users';
- Avoid global types: - // Instead of defining global
 declare global {
 type UserID = string;
 }
 // Define locally
 // types.ts
 export type UserID = string;
 // Use locally
 // user.ts
 import { UserID } from './types';
 const user: UserID = 'abc123';
Here are examples illustrating these additional TypeScript key points:
- Shared types: - // types/common.ts
 export type ID = string | number;
 // In different modules, you can import the shared type
 import { ID } from './types/common';
- Naming conventions: - // Clear and descriptive names for interface
 interface UserResponse {
 users: Array<User>;
 total: number;
 }
 // For generics
 interface Repository<T> {
 getById(id: ID): T;
 save(entity: T): void;
 }
- Generics: - // Generic function to return array elements or null
 function getFirst<T>(items: T[]): T | null {
 return items.length > 0 ? items[0] : null;
 }
- Type narrowing: - // Type guard to narrow type
 function isNumber(value: unknown): value is number {
 return typeof value === 'number';
 }
 // Usage
 const result: unknown = getResult();
 if (isNumber(result)) {
 console.log(result.toFixed(2)); // Type is now narrowed to 'number'
 }
- Extend with care: - // Extending native types can be confusing
 interface CustomArray<T> extends Array<T> {
 getFirstElement(): T | undefined;
 }
- Enums and constants: - // Using enums instead of literal types
 enum UserRole {
 Admin = 'ADMIN',
 User = 'USER',
 Guest = 'GUEST'
 }
- Documentation: - /**
 * Represents a user of the application.
 * @property {string} id - The unique identifier for a user.
 * @property {string} name - The name of the user.
 */
 interface User {
 id: string;
 name: string;
 }
These code samples provide a clear understanding of how to apply these TypeScript best practices. Remember that good documentation and consistent naming go a long way in maintaining a codebase, especially when dealing with complex type definitions and generics.
Understanding how to effectively organize type definitions in TypeScript is essential for maintaining a clean and manageable codebase, and for maximizing the benefit of TypeScript's powerful type system in larger projects.