Skip to content

A TypeScript Style Guide for writing clean, maintainable, and scalable code in TypeScript projects. This guide enforces best practices using TypeScript, ESLint, and Prettier while promoting consistency and readability across teams.

Notifications You must be signed in to change notification settings

DevrMichael/typescript-style-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 

Repository files navigation

TypeScript Style Guide

A structured approach to writing TypeScript code

This guide is based on best practices for writing clean, maintainable, and scalable TypeScript code. It includes specific rules and conventions to follow when writing TypeScript in React projects.

Introduction

What

Consistency is key to maintaining a high-quality, scalable, and maintainable codebase. This TypeScript Style Guide enforces best practices using automated tools like ESLint, TypeScript, and Prettier, while also providing design and architectural conventions that developers should follow.

Why

As projects grow, maintaining a uniform coding style and ensuring best practices become increasingly important. A well-defined style guide provides:

  • A consistent codebase, reducing technical debt.
  • Faster development cycles with fewer disputes over code style.
  • Easier onboarding for new developers by minimizing learning curves.
  • Improved readability and maintainability across the team.

Disclaimer

Like any style guide, this one is opinionated and may not fit every project perfectly. Use it as a baseline and adapt it as necessary to fit your team’s needs while maintaining internal consistency.

Requirements

This style guide assumes the use of:

  • TypeScript v5 or later
  • typescript-eslint with strict type-checking enabled
  • Prettier for automatic formatting
  • React for frontend conventions (though not required)

TL;DR - Quick Best Practices

✅ Prefer const for immutability and readonly properties

✅ Use union types over enums when possible

✅ Avoid any and prefer unknown or proper type definitions

✅ Use PascalCase for components and camelCase for variables/functions

✅ Keep functions pure, stateless, and single-responsibility

✅ Follow consistent naming conventions across the codebase

✅ Prefer explicit return types for functions

By following these conventions, your TypeScript projects will be more predictable, efficient, and scalable. 🚀


Table of Contents

  1. Basic Rules
  2. Type Annotations
  3. Interfaces vs. Types
  4. Class vs Functional Components
  5. Mixins
  6. Naming Conventions
  7. Declarations & Alignment
  8. Quotes & Spacing
  9. Props & State
  10. Refs & Parentheses
  11. Tags & Methods
  12. Component Ordering
  13. Enums

1. Basic Rules

  • Use TypeScript syntax and avoid any unless absolutely necessary.
  • Always define types for function parameters and return values.
  • Enable strict mode in tsconfig.json.
  • Use const and let instead of var.
  • Prefer readonly for immutable object properties.
  • Do not use namespace; use ES6 modules instead.
  • Avoid non-null assertions (!), except when absolutely necessary.

2. Type Annotations

  • Always specify types explicitly when the type is not inferred.
  • Avoid redundant type annotations where TypeScript can infer them.
// Bad
let age: number = 25;

// Good
let age = 25; // TypeScript infers `number`
  • Use unknown instead of any where applicable.
  • Use never for functions that never return.
// Bad
function throwError(message: string): void {
  throw new Error(message);
}

// Good
function throwError(message: string): never {
  throw new Error(message);
}

3. Interfaces vs. Types

  • Use interface for defining object shapes.
  • Use type for unions, intersections, or primitive-based types.
// Bad
type User = {
  id: number;
  name: string;
};

// Good
interface User {
  id: number;
  name: string;
}
  • Use extends for interface inheritance.
// Bad: Using type for extending an object type
type AdminProps = UserProps & {
  role: "admin" | "super-admin";
};

// Good: Using interface with extends
interface AdminProps extends UserProps {
  role: "admin" | "super-admin";
}

const AdminCard: React.FC<AdminProps> = ({ id, name, role }) => {
  return (
    <div>
      <h3>Admin ID: {id}</h3>
      <p>Name: {name}</p>
      <p>Role: {role}</p>
    </div>
  );
};

export default AdminCard;

4. Class vs Functional Components

  • Prefer function components over class components.
  • Use React.FC<T> for defining functional components with props.
// Bad
class UserProfile extends React.Component<{ name: string }> {
  render() {
    return <div>Hello, {this.props.name}</div>;
  }
}

// Good
const UserProfile: React.FC<{ name: string }> = ({ name }) => {
  return <div>Hello, {name}</div>;
};
  • Use hooks instead of class-based lifecycle methods.

5. Mixins

  • Do not use mixins.
  • Prefer Higher-Order Components (HOCs) or custom hooks.
// Bad
const WithLogging = (Base: any) => class extends Base {
  log() {
    console.log("Logging...");
  }
};

// Good
function useLogging() {
  useEffect(() => {
    console.log("Logging...");
  }, []);
}

6. Naming Conventions

  • Use PascalCase for components.
// ❌ Bad: Using lowercase or camelCase for a component name
function userprofile() {
  return <div>User Profile</div>;
}

export default userprofile;

// ✅ Good: Using PascalCase for the component name
function UserProfile() {
  return <div>User Profile</div>;
}

export default UserProfile;
  • Use camelCase for variables and functions.
// ❌ Bad: Using uppercase or PascalCase for variables
const UserName = "Alice";
function GetUserAge() {
  return 30;
}

// ✅ Good: Use camelCase for variables and functions
const userName = "Alice";
function getUserAge() {
  return 30;
}
  • Use T prefix for generic type parameters.
// ❌ Bad: Generic type has no `T` prefix
function getFirstItem<Type>(arr: Type[]): Type {
  return arr[0];
}

// ✅ Good: Generic type prefixed with `T`
function getFirstItem<T>(arr: T[]): T {
  return arr[0];
}
  • Use meaningful names.
  // ❌ Bad: Unclear variable names
const x = "John";
const d = new Date();
function doSomething() {
  return "Hello";
}

// ✅ Good: Meaningful variable and function names
const userName = "John";
const currentDate = new Date();
function generateGreeting() {
  return "Hello";
}
  • Naming Conventions in Props & State
// ❌ Bad: Non-standard prop names
interface UserProps {
  User_Name: string;
  AGE: number;
}

// ✅ Good: camelCase for props
interface UserProps {
  userName: string;
  age: number;
}

7. Declarations & Alignment

  • Use single responsibility per file.
// Bad: Multiple components in a single file
const Header = () => <header>Header</header>;
const Footer = () => <footer>Footer</footer>;

export { Header, Footer };

// Good: Separate files for each component
// Header.tsx
const Header = () => <header>Header</header>;
export default Header;

// Footer.tsx
const Footer = () => <footer>Footer</footer>;
export default Footer;
  • Use export default for components.
// Bad: Named export for a single component
export const Button = () => <button>Click me</button>;

// Good: Default export for a single component
const Button = () => <button>Click me</button>;
export default Button;
  • Maintain consistent indentation and spacing.

8. Quotes & Spacing

  • Use single quotes for JavaScript/TypeScript.
// Bad
const message = "Hello World";

// Good
const message = 'Hello World';
  • Use double quotes for JSX attributes.
// Bad
<Button label='Click me' />;

// Good
<Button label="Click me" />;
  • Do not pad JSX curly braces.
// Bad
<Component prop={ someValue } />;

// Good
<Component prop={someValue} />;

9. Props & State

  • Always use interfaces or types for props.
// Bad: No type definition
const Button = (props) => <button>{props.label}</button>;

// Good: Using an interface for props
interface ButtonProps {
  label: string;
}

const Button: React.FC<ButtonProps> = ({ label }) => <button>{label}</button>;
  • Avoid any; prefer unknown when necessary.
// Bad
function processInput(input: any) {
  console.log(input);
}

// Good
function processInput(input: unknown) {
  if (typeof input === 'string') {
    console.log(input.toUpperCase());
  }
}
  • Use defaultProps or default function parameters for optional props.
// Bad
interface UserProps {
  name?: string;
}

const User = ({ name }: UserProps) => <div>{name ? name : 'Guest'}</div>;

// Good
interface UserProps {
  name?: string;
}

const User = ({ name = 'Guest' }: UserProps) => <div>{name}</div>;

10. Refs & Parentheses

  • Use ref callbacks over string refs.
// Bad
<input ref="myInput" />;

// Good
const inputRef = useRef<HTMLInputElement>(null);
<input ref={inputRef} />;
  • Wrap JSX in parentheses when multiline.
// Bad
return <div>
  <Header />
  <Main />
  <Footer />
</div>;

// Good
return (
  <div>
    <Header />
    <Main />
    <Footer />
  </div>
);

11. Tags & Methods

  • Use self-closing tags where applicable.
// Bad
<img src="image.jpg"></img>;

// Good
<img src="image.jpg" />;
  • Use arrow functions for event handlers.
// Bad
function handleClick() {
  console.log('Clicked');
}

// Good
const handleClick = () => {
  console.log('Clicked');
};

12. Component Ordering

For class components, order methods as follows:

  1. State and Refs (using useState and useRef)
  2. Derived Values and Computed Variables
  3. Effects (useEffect)
  4. Event Handlers
  5. Render Logic (JSX return statement)
   const ExampleComponent: React.FC = () => {
  // 1. State and Refs
  const [count, setCount] = useState(0);
  const buttonRef = useRef<HTMLButtonElement>(null);

  // 2. Derived Values and Computed Variables
  const buttonLabel = `Clicked ${count} times`;

  // 3. Effects
  useEffect(() => {
    console.log('Component mounted');
  }, []);

  // 4. Event Handlers
  const handleClick = () => {
    setCount((prevCount) => prevCount + 1);
  };

  // 5. Render Logic
  return (
    <button ref={buttonRef} onClick={handleClick}>
      {buttonLabel}
    </button>
  );
};

export default ExampleComponent;
```ts

### Why This Order?
- Keeps related logic grouped together
- Improves readability and maintainability
- Prevents unnecessary re-renders by optimizing state updates

---

## 13. Enums

Enums allow defining a set of named constants. Use them when a variable can take one of a few predefined values.

### Numeric Enums

```ts
enum Status {
  Pending,
  InProgress,
  Completed
}

String Enums

enum Status {
  Pending = "Pending",
  InProgress = "InProgress",
  Completed = "Completed"
}

Prefer Union Types

type Status = "Pending" | "InProgress" | "Completed";

Conclusion

This style guide ensures your TypeScript code follows best practices while staying maintainable and scalable. Adhering to these conventions will improve consistency across projects and facilitate collaboration among developers.

About

A TypeScript Style Guide for writing clean, maintainable, and scalable code in TypeScript projects. This guide enforces best practices using TypeScript, ESLint, and Prettier while promoting consistency and readability across teams.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published