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.
# 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# Create tsconfig.json
npx tsc --init
# Or create custom config
npx tsc --init --target es2020 --lib es2020,dom --module esnext --moduleResolution node{
"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"
]
}{
"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"
}// 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>>;// 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 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';
}// 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
}// 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; }// 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'// 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
}
}// 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 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>
);
}// 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} />// 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';// 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
}
}// 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'],
},
};// ❌ 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';
}// ❌ 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';
}// ❌ 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];
}// ❌ 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';# Install type coverage tool
npm install --save-dev type-coverage
# Check type coverage
npx type-coverage
# Generate detailed report
npx type-coverage --detail// 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();# .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