Skip to content

Latest commit

 

History

History
663 lines (559 loc) · 15.2 KB

File metadata and controls

663 lines (559 loc) · 15.2 KB

TypeScript Optimization Guide

TypeScript provides type safety, better developer experience, and helps catch errors at compile time. This guide covers setup, configuration, and optimization techniques for TypeScript in frontend projects.

🚀 Getting Started

Installation

# Install TypeScript and types
npm install --save-dev typescript @types/node @types/react @types/react-dom

# For specific libraries
npm install --save-dev @types/lodash @types/express @types/jest

# Install TypeScript ESLint
npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin

Initialization

# Create tsconfig.json
npx tsc --init

# Or create custom config
npx tsc --init --target es2020 --lib es2020,dom --module esnext --moduleResolution node

⚙️ TypeScript Configuration

Basic tsconfig.json

{
  "compilerOptions": {
    // Target and Module
    "target": "ES2020",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    
    // Output
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    
    // Strict Type Checking
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "exactOptionalPropertyTypes": true,
    
    // Advanced
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    
    // JSX
    "jsx": "react-jsx",
    "jsxImportSource": "react"
  },
  "include": [
    "src/**/*",
    "types/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "build",
    "**/*.test.ts",
    "**/*.spec.ts"
  ]
}

Advanced Configuration

{
  "compilerOptions": {
    // Path mapping
    "baseUrl": "./src",
    "paths": {
      "@/*": ["*"],
      "@/components/*": ["components/*"],
      "@/utils/*": ["utils/*"],
      "@/types/*": ["types/*"],
      "@/hooks/*": ["hooks/*"],
      "@/store/*": ["store/*"]
    },
    
    // Performance optimizations
    "incremental": true,
    "tsBuildInfoFile": ".tsbuildinfo",
    
    // Experimental features
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    
    // Module resolution
    "allowImportingTsExtensions": false,
    "noResolve": false,
    "typeRoots": ["./node_modules/@types", "./types"],
    "types": ["node", "jest"]
  },
  
  // Project references for monorepos
  "references": [
    { "path": "./packages/shared" },
    { "path": "./packages/ui" },
    { "path": "./apps/web" }
  ],
  
  // Extend configurations
  "extends": "./base.json"
}

🎯 Type Definitions

Basic Types

// Primitive types
type ID = string | number;
type Status = 'pending' | 'completed' | 'failed';
type Optional<T> = T | null | undefined;

// Function types
type EventHandler<T = HTMLElement> = (event: React.ChangeEvent<T>) => void;
type AsyncFunction<T, R> = (param: T) => Promise<R>;

// Utility types
type Partial<T> = { [P in keyof T]?: T[P] };
type Required<T> = { [P in keyof T]-?: T[P] };
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

React Component Types

// Component props
interface ButtonProps {
  children: React.ReactNode;
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
  onClick?: () => void;
  className?: string;
}

// Generic component
interface ListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  keyExtractor: (item: T) => string | number;
  emptyMessage?: string;
}

// Ref forwarding
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ children, variant = 'primary', size = 'md', ...props }, ref) => {
    return (
      <button
        ref={ref}
        className={`btn btn-${variant} btn-${size}`}
        {...props}
      >
        {children}
      </button>
    );
  }
);

API and Data Types

// API response types
interface ApiResponse<T> {
  data: T;
  message: string;
  success: boolean;
  errors?: string[];
}

// User types
interface User {
  id: string;
  email: string;
  name: string;
  avatar?: string;
  createdAt: Date;
  updatedAt: Date;
}

// Form types
interface LoginForm {
  email: string;
  password: string;
  rememberMe: boolean;
}

interface RegisterForm extends LoginForm {
  confirmPassword: string;
  firstName: string;
  lastName: string;
  acceptTerms: boolean;
}

// State types
interface AppState {
  user: User | null;
  isLoading: boolean;
  error: string | null;
  theme: 'light' | 'dark';
}

🛠️ Advanced TypeScript Features

Conditional Types

// Conditional type utilities
type NonNullable<T> = T extends null | undefined ? never : T;
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Parameters<T> = T extends (...args: infer P) => any ? P : never;

// Use cases
type ApiEndpoint<T> = T extends 'users' 
  ? User[] 
  : T extends 'posts' 
  ? Post[] 
  : never;

function fetchData<T extends string>(endpoint: T): Promise<ApiEndpoint<T>> {
  // Implementation
}

Mapped Types

// Make all properties optional
type Partial<T> = {
  [P in keyof T]?: T[P];
};

// Make all properties required
type Required<T> = {
  [P in keyof T]-?: T[P];
};

// Make all properties readonly
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

// Custom mapped type
type Stringify<T> = {
  [K in keyof T]: string;
};

interface User {
  id: number;
  name: string;
  age: number;
}

type StringifiedUser = Stringify<User>; // { id: string; name: string; age: string; }

Template Literal Types

// String manipulation types
type Capitalize<S extends string> = S extends `${infer F}${infer R}` 
  ? `${Uppercase<F>}${R}` 
  : S;

type Uncapitalize<S extends string> = S extends `${infer F}${infer R}` 
  ? `${Lowercase<F>}${R}` 
  : S;

// CSS class names
type CSSClass<T extends string> = `css-${T}`;
type ButtonVariant = 'primary' | 'secondary' | 'danger';
type ButtonClass = CSSClass<ButtonVariant>; // 'css-primary' | 'css-secondary' | 'css-danger'

// Event names
type EventName<T extends string> = `on${Capitalize<T>}`;
type ButtonEvents = EventName<'click' | 'hover' | 'focus'>; // 'onClick' | 'onHover' | 'onFocus'

Discriminated Unions

// Loading states
type LoadingState = 
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: any }
  | { status: 'error'; error: string };

// API responses
type ApiResult<T> = 
  | { success: true; data: T }
  | { success: false; error: string; code: number };

// Form validation
type ValidationResult = 
  | { valid: true }
  | { valid: false; errors: string[] };

// Usage with type guards
function handleLoadingState(state: LoadingState) {
  switch (state.status) {
    case 'idle':
      return 'Ready to load';
    case 'loading':
      return 'Loading...';
    case 'success':
      return `Data: ${state.data}`; // TypeScript knows data exists
    case 'error':
      return `Error: ${state.error}`; // TypeScript knows error exists
  }
}

🎯 React-Specific Patterns

Hooks with TypeScript

// Custom hook with types
function useLocalStorage<T>(key: string, initialValue: T) {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(`Error reading localStorage key "${key}":`, error);
      return initialValue;
    }
  });

  const setValue = useCallback((value: T | ((val: T) => T)) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(`Error setting localStorage key "${key}":`, error);
    }
  }, [key, storedValue]);

  return [storedValue, setValue] as const;
}

// Usage
const [user, setUser] = useLocalStorage<User | null>('user', null);
const [theme, setTheme] = useLocalStorage<'light' | 'dark'>('theme', 'light');

Context with TypeScript

// Context type
interface AppContextType {
  user: User | null;
  theme: 'light' | 'dark';
  setUser: (user: User | null) => void;
  setTheme: (theme: 'light' | 'dark') => void;
}

// Context creation
const AppContext = createContext<AppContextType | undefined>(undefined);

// Custom hook for context
function useAppContext() {
  const context = useContext(AppContext);
  if (context === undefined) {
    throw new Error('useAppContext must be used within an AppProvider');
  }
  return context;
}

// Provider component
interface AppProviderProps {
  children: React.ReactNode;
}

function AppProvider({ children }: AppProviderProps) {
  const [user, setUser] = useState<User | null>(null);
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  const value: AppContextType = {
    user,
    theme,
    setUser,
    setTheme,
  };

  return (
    <AppContext.Provider value={value}>
      {children}
    </AppContext.Provider>
  );
}

Higher-Order Components

// HOC with TypeScript
function withLoading<P extends object>(
  Component: React.ComponentType<P>
) {
  return function WithLoadingComponent(
    props: P & { isLoading?: boolean }
  ) {
    const { isLoading, ...rest } = props;
    
    if (isLoading) {
      return <div>Loading...</div>;
    }
    
    return <Component {...(rest as P)} />;
  };
}

// Usage
const UserProfile = ({ user }: { user: User }) => (
  <div>{user.name}</div>
);

const UserProfileWithLoading = withLoading(UserProfile);

// Type-safe props
<UserProfileWithLoading user={user} isLoading={false} />

🔧 Performance Optimizations

Type-Only Imports

// Import types only (removed at compile time)
import type { User, ApiResponse } from './types';
import type { ComponentProps } from 'react';

// Mixed imports
import React, { type FC, type PropsWithChildren } from 'react';

// Re-export types
export type { User, ApiResponse } from './types';

Compiler Optimizations

// tsconfig.json optimizations
{
  "compilerOptions": {
    // Faster compilation
    "incremental": true,
    "tsBuildInfoFile": ".tsbuildinfo",
    "skipLibCheck": true,
    
    // Smaller output
    "removeComments": true,
    "stripInternal": true,
    
    // Better tree shaking
    "importsNotUsedAsValues": "error",
    "preserveValueImports": false
  }
}

Build Optimization

// webpack.config.js with TypeScript optimization
module.exports = {
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: [
          {
            loader: 'ts-loader',
            options: {
              transpileOnly: true, // Faster builds
              experimentalWatchApi: true, // Faster watch mode
            },
          },
        ],
      },
    ],
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.jsx'],
  },
};

🚨 Common Issues & Solutions

1. Strict Null Checks

// ❌ Problem: Potential null/undefined access
function getName(user: User | null): string {
  return user.name; // Error: Object is possibly 'null'
}

// ✅ Solution: Null checks
function getName(user: User | null): string {
  if (!user) {
    return 'Unknown';
  }
  return user.name;
}

// ✅ Solution: Optional chaining
function getName(user: User | null): string {
  return user?.name ?? 'Unknown';
}

2. Type Assertions

// ❌ Problem: Unsafe type assertion
const element = document.getElementById('myElement') as HTMLInputElement;
element.value = 'test'; // Might crash if element is not input

// ✅ Solution: Type guards
function isHTMLInputElement(element: HTMLElement | null): element is HTMLInputElement {
  return element instanceof HTMLInputElement;
}

const element = document.getElementById('myElement');
if (isHTMLInputElement(element)) {
  element.value = 'test'; // Safe
}

// ✅ Solution: Null checks
const element = document.getElementById('myElement');
if (element && element instanceof HTMLInputElement) {
  element.value = 'test';
}

3. Generic Constraints

// ❌ Problem: Unconstrained generic
function getProperty<T>(obj: T, key: string) {
  return obj[key]; // Error: Index signature not found
}

// ✅ Solution: Constrain generic
function getProperty<T extends Record<string, any>>(obj: T, key: keyof T): T[keyof T] {
  return obj[key];
}

// ✅ Solution: Use specific constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

4. Module Resolution

// ❌ Problem: Module not found
import { MyComponent } from './MyComponent'; // Error if file doesn't exist

// ✅ Solution: Check file extensions and paths
import { MyComponent } from './MyComponent.tsx';

// ✅ Solution: Use path mapping in tsconfig.json
// "paths": { "@/*": ["src/*"] }
import { MyComponent } from '@/components/MyComponent';

📊 Type Coverage and Metrics

Type Coverage Analysis

# Install type coverage tool
npm install --save-dev type-coverage

# Check type coverage
npx type-coverage

# Generate detailed report
npx type-coverage --detail

Custom Type Coverage Script

// scripts/type-coverage.js
const { execSync } = require('child_process');
const fs = require('fs');

function checkTypeCoverage() {
  try {
    const output = execSync('npx type-coverage --detail', { encoding: 'utf8' });
    const lines = output.split('\n');
    
    const coverageLine = lines.find(line => line.includes('Coverage:'));
    const coverage = coverageLine ? coverageLine.match(/(\d+(?:\.\d+)?)%/)?.[1] : null;
    
    if (coverage) {
      const percentage = parseFloat(coverage);
      console.log(`Type Coverage: ${percentage}%`);
      
      // Save to file
      fs.writeFileSync('type-coverage.json', JSON.stringify({
        coverage: percentage,
        timestamp: new Date().toISOString(),
      }, null, 2));
      
      // Fail if coverage is too low
      if (percentage < 80) {
        console.error('Type coverage is below 80%');
        process.exit(1);
      }
    }
  } catch (error) {
    console.error('Error checking type coverage:', error.message);
    process.exit(1);
  }
}

checkTypeCoverage();

CI/CD Integration

# .github/workflows/typescript.yml
name: TypeScript Check
on: [push, pull_request]

jobs:
  type-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm ci
      - run: npm run type-check
      - run: npm run lint
      - run: npx type-coverage

📚 Additional Resources