Best Practices
## Learning Objectives
- Write clean, maintainable TypeScript
- Follow naming conventions
- Master type safety
- Apply effective testing
- Migrate from JavaScript
## Code Style
### Naming Conventions
```typescript
// Classes: PascalCase
class BankAccount { }
// Interfaces: PascalCase (or sometimes I prefix)
interface UserService { }
interface IUser { } // Alternative: with I prefix
// Variables and functions: camelCase
let accountBalance: number;
function calculateInterest() { }
// Constants: UPPER_SNAKE_CASE
const MAX_RETRY_COUNT = 3;
const API_BASE_URL = "https://api.example.com";
// Type aliases and enums: PascalCase
type UserRole = "admin" | "user";
enum HttpStatus { Ok, NotFound }
```
### Formatting
```typescript
// Use 2 or 4 spaces consistently
// Line length: ~100 characters max
// One declaration per line
let age: number;
let name: string;
// Braces always on same line
if (condition) {
doSomething();
} else {
doOther();
}
```
## Type Safety
### Strict Mode
```json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true
}
}
```
### Avoid Any
```typescript
// Bad
function processData(data: any): any {
return data;
}
// Good
function processData(data: T): T {
return data;
}
// If you must use any
function processData(data: unknown): unknown {
return data;
}
```
### Type Inference
```typescript
// TypeScript infers: let x = 10 is number
let x = 10;
// x = "hello"; // Error
// Explicit when needed
let a: number | string;
a = 10;
a = "hello";
```
## Interfaces vs Type Aliases
### Use Interface for Objects
```typescript
interface User {
id: number;
name: string;
email: string;
}
```
### Use Type for Unions and Tuples
```typescript
type ID = string | number;
type Point = [number, number];
type Callback = () => void;
```
## Functions
### Typed Parameters and Returns
```typescript
// Good: explicit types
function add(a: number, b: number): number {
return a + b;
}
// Bad: relying on inference
const add = (a, b) => a + b;
```
### Optional Parameters
```typescript
// Optional with ?
function greet(name?: string): string {
return name ? `Hello, ${name}!` : "Hello!";
}
// Default values
function createUser(name: string, role: string = "user") {
return { name, role };
}
```
## Classes
### Encapsulation
```typescript
class BankAccount {
private balance: number = 0;
constructor(initialBalance: number = 0) {
this.balance = initialBalance;
}
deposit(amount: number): void {
if (amount > 0) {
this.balance += amount;
}
}
withdraw(amount: number): boolean {
if (amount > 0 && amount <= this.balance) {
this.balance -= amount;
return true;
}
return false;
}
getBalance(): number {
return this.balance;
}
}
```
### readonly for Constants
```typescript
class Config {
readonly API_URL = "https://api.example.com";
readonly MAX_RETRIES = 3;
}
```
## Enums
### Use Const Enums
```typescript
const enum Status {
Active,
Pending,
Completed
}
if (status === Status.Active) {
// ...
}
```
### String Enums for Flexibility
```typescript
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
```
## Generics
### Constrain When Needed
```typescript
// Generic with constraint
function logLength(arg: T): T {
console.log(arg.length);
return arg;
}
logLength("hello"); // OK
logLength([1, 2, 3]); // OK
// logLength(123); // Error
```
### Use Utility Types
```typescript
// Don't reimplement
type PartialUser = Partial;
type UserPreview = Pick;
type UserWithoutEmail = Omit;
```
## Error Handling
### Specific Types
```typescript
try {
JSON.parse(invalidJson);
} catch (error) {
if (error instanceof SyntaxError) {
console.error("JSON syntax error:", error.message);
} else {
throw error;
}
}
```
### Result Pattern
```typescript
type Result =
| { success: true; value: T }
| { success: false; error: E };
function divide(a: number, b: number): Result {
if (b === 0) {
return { success: false, error: new Error("Division by zero") };
}
return { success: true, value: a / b };
}
```
## Null Handling
### Optional Chaining
```typescript
interface User {
address?: {
city: string;
};
}
let city = user?.address?.city; // undefined if missing
```
### Nullish Coalescing
```typescript
let name = userName ?? "Anonymous";
let count = items.length ?? 0;
```
### Avoid Non-null Assertion
```typescript
// Risky
let name = value!;
// Better
if (value !== null && value !== undefined) {
let name = value;
}
```
## Migration from JavaScript
### Step 1: Add tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": false,
"allowJs": true
}
}
```
### Step 2: Enable checkJs
```json
{
"compilerOptions": {
"checkJs": true
}
}
```
### Step 3: Add Type Annotations
```javascript
// Before (JavaScript)
/ function add(a, b) {
return a + b;
}
```
```typescript
// After (TypeScript)
function add(a: number, b: number): number {
return a + b;
}
```
### Step 4: Enable Strict Mode
```json
{
"compilerOptions": {
"strict": true
}
}
```
## Testing
### With Jest
```typescript
// calculator.ts
export function add(a: number, b: number): number {
return a + b;
}
// calculator.test.ts
import { add } from "./calculator";
test("adds two numbers", () => {
expect(add(2, 3)).toBe(5);
});
```
### Typed Mocks
```typescript
interface UserService {
getUser(id: number): Promise;
}
function getUserWithCache(
id: number,
service: UserService
): Promise {
// Implementation
}
```
## Documentation
### JSDoc with Types
```typescript
/**
* Calculates the area of a rectangle.
*
* @param width - The width of the rectangle
* @param height - The height of the rectangle
* @returns The area (width * height)
* @throws {Error} If width or height is negative
*/
function rectangleArea(width: number, height: number): number {
if (width < 0 || height < 0) {
throw new Error("Dimensions must be positive");
}
return width * height;
}
```
## Tooling
### ESLint
```json
{
"parser": "@typescript-eslint/parser",
"rules": {
"no-unused-vars": "error",
"prefer-const": "error"
}
}
```
### Prettier
```json
{
"semi": true,
"singleQuote": true,
"trailingComma": "es5"
}
```
## Summary
- Enable `strict: true` for type safety
- Prefer interfaces for object shapes
- Use type for unions, tuples, primitives
- Avoid `any`, use `unknown` instead
- Use optional chaining and nullish coalescing
- Write pure functions when possible
- Use `readonly` for constants
- Document public APIs with JSDoc
- Test typed functions
- Migrate gradually from JavaScript
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →