Tuesday, January 7, 2025

20 Tips and Tricks Typescripts with clean code best practices

20 Tips for Writing Clear and Efficient TypeScript Code

To write maintainable and efficient TypeScript code, it's essential to utilize the language's powerful features. This article dives into 20 key tips, complete with comprehensive explanations and illustrative code examples. These TypeScript tips will help improve TypeScript code readability, ensure TypeScript code optimization, and adhere to TypeScript coding guidelines.


1. Utility Types: Partial, Pick, Omit, Readonly, & Required

TypeScript provides utility types that make it easier to work with existing types by transforming them into new types. These are crucial for TypeScript development and improving TypeScript code quality.

Partial

Makes all properties optional:


interface User {
  name: string;
  age: number;
  address?: string;
}

const updateUser = (user: Partial): void => {
  console.log(user);
};

updateUser({ name: 'John' });

Pick

Selects specific properties:


type UserPreview = Pick;
const preview: UserPreview = { name: 'Alice', age: 25 };

Omit

Excludes properties:


type UserWithoutAddress = Omit;
const user: UserWithoutAddress = { name: 'Alice', age: 30 };

Readonly

Prevents reassignment of properties:


const user: Readonly = { name: 'Bob', age: 40 };
// user.age = 41; // Error: Cannot assign to 'age'

Required

Makes all properties mandatory:


type CompleteUser = Required;
const user: CompleteUser = { name: 'Alice', age: 25, address: '123 Street' };


2. Generics

Generics enable reusable and type-safe components. They are an integral part of TypeScript programming, ensuring type safety and flexibility.


function identity(value: T): T {
  return value;
}

const numberResult = identity(42);
const stringResult = identity('Hello');

Use generics in classes:


class Box {
  content: T;
  constructor(value: T) {
    this.content = value;
  }
}

const stringBox = new Box('Content');
console.log(stringBox.content);


3. Using Strict Typing Options

Enable strict typing options in your tsconfig.json for enhanced type safety. This is a must-follow best practice for TypeScript development to maintain clean TypeScript code.


{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}

These settings catch errors during development, ensuring cleaner and safer TypeScript programming.


4. Use unknown over any

unknown ensures type checking before usage, helping improve TypeScript code quality and safety:


let input: unknown;
input = 'hello';

if (typeof input === 'string') {
  console.log(input.toUpperCase());
}

Avoid any as it bypasses type checking:


let value: any;
value = 42;
console.log(value.toUpperCase()); // Runtime error


5. Switch Case Condition with Exhaustive Checks

Use never to ensure all cases are handled. This technique is vital for improving TypeScript code readability and preventing unhandled edge cases:


function getStatus(status: 'success' | 'error'): string {
  switch (status) {
    case 'success':
      return 'Operation was successful';
    case 'error':
      return 'Operation failed';
    default:
      const _exhaustiveCheck: never = status;
      throw new Error(`Unhandled case: ${status}`);
  }
}


6. Use Readonly and Immutable Types for Safety

Prevent unintended modifications by using readonly. This is one of the advanced TypeScript coding techniques to ensure immutability:


const user: Readonly = { name: 'Alice', age: 30 };
// user.age = 31; // Error

For arrays, use ReadonlyArray:


const numbers: ReadonlyArray = [1, 2, 3];
// numbers.push(4); // Error


7. Define Return Types Explicitly

Always define return types explicitly to enhance code readability and avoid implicit returns.


function add(a: number, b: number): number {
  return a + b;
}

// Avoid
function subtract(a: number, b: number) {
  return a - b; // Implicitly inferred as 'number'
}


8. Handle Null/Undefined Scenarios with Optional Chaining and Nullish Coalescing

Optional chaining (?.) and nullish coalescing (??) simplify handling null or undefined values:


const user = {
  name: 'Alice',
  address: {
    street: '123 Main St'
  }
};

console.log(user.address?.street); // '123 Main St'
console.log(user.contact?.phone); // undefined

const value = user.age ?? 30;
console.log(value); // 30


9. Use never for Exhaustive Checks in Switch

Prevent unhandled edge cases by using never:


type Status = 'success' | 'error';

function processStatus(status: Status): void {
  switch (status) {
    case 'success':
      console.log('Success');
      break;
    case 'error':
      console.log('Error');
      break;
    default:
      const exhaustiveCheck: never = status;
      throw new Error(`Unhandled status: ${status}`);
  }
}


10. Use Immutable Types as const

Leverage const for variables that should not be reassigned:


const PI = 3.14;
// PI = 3.1415; // Error: Cannot reassign a constant

const config = { url: 'https://api.example.com' } as const;
// config.url = 'https://api.changed.com'; // Error


11. Intersection Types

Combine multiple types into one using intersections:


interface Person {
  name: string;
}
interface Employee {
  employeeId: number;
}

type Staff = Person & Employee;
const staff: Staff = { name: 'John', employeeId: 123 };

TypeScript, with its static typing, brings a wealth of features to JavaScript development, enhancing code reliability and development experience. In this blog post, we'll explore several TypeScript tips that can make your coding life easier and your software more robust.

12. Intersection for Types
Intersections allow you to combine multiple types into one, which is particularly useful for defining complex object shapes that need to satisfy multiple interfaces:

typescript
interface ErrorHandling {
    success: boolean;
    error?: { message: string };
}

interface Config {
    host: string;
    port: number;
}

type Operation = ErrorHandling & Config;

const operation: Operation = {
    success: true,
    host: "localhost",
    port: 8080
};

13. Index Signatures
Index signatures are handy for defining objects where you don't know all the keys in advance but know the type of values:

typescript
interface StringArray {
    [index: number]: string;
}

let myArray: StringArray = [];
myArray[0] = "Hello";
myArray[1] = "World";

14. Type Guards
Type guards let you narrow down the type within a conditional block, which helps in handling different types safely:

typescript
function padLeft(value: string | number, padding: string | number) {
    if (typeof value === "string") {
        return padding.toString() + value;
    }
    if (typeof padding === "string") {
        return padding + value.toString();
    }
    return padding.toString() + value.toString();
}

15. Prefer Functional Programming Techniques
TypeScript supports functional programming, which can lead to cleaner, more predictable code:

typescript
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(num => num * 2);

16. Error Handling with Never - ThrowErrors -> Never
Using never for functions that only throw errors can clarify that a function will never return normally:

typescript
function fail(msg: string): never {
    throw new Error(msg);
}

function checkExhaustiveness(x: never): never {
    throw new Error("Unexpected value: " + x);
}

17. Use of Async/Await
Async/await syntax makes asynchronous code look and behave more like synchronous code, improving readability:

typescript
async function fetchData() {
    try {
        const response = await fetch('url');
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error('Error:', error);
    }
}

18. Clean Code with Promises over Nested Callbacks
Promises help avoid "callback hell" by providing a cleaner way to handle asynchronous operations:

typescript
function getPost(postId) {
    return new Promise(resolve => {
        setTimeout(() => resolve({ id: postId, title: "Post Title" }), 1000);
    });
}

getPost(1).then(post => console.log(post.title));

19. Avoid Negative Conditionals
Using positive conditionals can make your code more readable and less error-prone:

typescript
// Instead of:
if (!isUserAdmin(user)) {
    // do something
}

// Use:
if (isUserGuest(user)) {
    // do something
}

20. Use Method Chaining
Method chaining can make your code more readable and concise:

typescript
class StringBuilder {
    value = '';
    append(str) {
        this.value += str;
        return this;
    }
    prepend(str) {
        this.value = str + this.value;
        return this;
    }
}

const builder = new StringBuilder();
console.log(builder.append('World').prepend('Hello ').value);

Conclusion: By adopting these TypeScript practices, you'll not only improve the quality of your code but also enhance your development workflow. TypeScript's features are there to make your JavaScript development safer and more productive. Keep practicing, and soon these tips will become second nature!

Tags: TypeScript, JavaScript, Programming Tips, Clean Code, Async/Await, Functional Programming

Remember to customize the post to fit your personal blogging style or the specific focus of your blog. Also, ensure any code snippets are formatted correctly for the Google Blogger platform, which might require using HTML code tags or a specific plugin for syntax highlighting.