Wednesday, October 16, 2024

Top 10 Bad Habit when Coding on Typescript 2024

TypeScript has become the language of choice for many developers, offering the benefits of static typing while retaining the flexibility of JavaScript. However, as TypeScript continues to evolve, some practices once considered best may now be outdated or suboptimal. This article will delve into 10 bad TypeScript habits you should break in 2024 to write cleaner, more efficient, and maintainable code.

1. Ignoring Strict Mode

Many developers disable TypeScript's strict mode to avoid dealing with stricter type checks. This, however, undermines the effectiveness of type safety. When strict mode is off, TypeScript becomes more lenient, allowing for potential unexpected bugs and making refactoring more challenging in the future.

Enabling strict mode in your tsconfig.json is a crucial step. It enforces better type checks, ensuring that your code is more robust and reliable in the long run.

{
  "compilerOptions": {
    "strict": true
  }
}
    

2. Overreliance on the any Type

The any type is often used as a quick fix when developers don't want to define the correct type. However, this practice effectively turns TypeScript into JavaScript, bypassing its core purpose of providing type safety. This can lead to runtime errors that TypeScript could have otherwise prevented.

Instead of any, utilize more specific types like unknown. The unknown type is safer than any because it forces type-checking before using the value. Ideally, you should define a proper type for your data.

let data: unknown;

if (typeof data === "string") {
  console.log(data.toUpperCase());
}
    

3. Careless Use of Type Assertions

Type assertions, using the as keyword, can be overused, especially when developers are unsure about types and aim to silence TypeScript errors. This can lead to unsafe code because it tells TypeScript to treat a value as a specific type even if it might not be. This can result in runtime issues if the value doesn't match the expectation.

Limiting type assertions is essential. Instead, use type guards, which allow you to safely check and infer types before using them.

function isString(value: unknown): value is string {
  return typeof value === 'string';
}

if (isString(data)) {
  console.log(data.toUpperCase());
}
    

4. Neglecting Union and Intersection Types

Union (|) and intersection (&) types provide powerful tools for describing complex data structures more concisely. Avoiding them leads to verbose code and missed opportunities for TypeScript's expressiveness.

Leverage these types to create flexible and reusable type definitions.

type Admin = { isAdmin: true; privileges: string[] };
type User = { isAdmin: false; email: string };

type Person = Admin | User;

function logUser(person: Person) {
  if (person.isAdmin) {
    console.log(person.privileges);
  } else {
    console.log(person.email);
  }
}
    

5. Non-Specific Return Types

Functions often have return types of any, object, or other non-specific types, forcing the consumer of the function to guess the expected return value. This makes the code less predictable and harder to debug, losing the benefit of TypeScript's static type checking.

Always specify the exact return type of a function. If the function returns multiple types, use union types to describe them.

function fetchData(): Promise<{ id: number; name: string }> {
  return fetch("/data").then(response => response.json());
}
    

6. Ignoring null and undefined

JavaScript allows variables to be null or undefined. While TypeScript provides tools to handle these values explicitly, ignoring them can lead to runtime errors when accessing properties or calling methods on null or undefined values.

Use optional chaining (?.) and the nullish coalescing operator (??) to safely handle null and undefined values.

      const name = user?.profile?.name ?? "Guest";
    

7. Overusing Enums

Enums are often overused for simple constant values, when other types like const or literal unions could suffice. This can add unnecessary complexity and even introduce issues related to mutability.

Use literal types or const objects for simple constant values.

type Role = "Admin" | "User" | "Guest";

let userRole: Role = "Admin";
    

8. Failing to Use readonly

The readonly keyword helps enforce immutability, preventing accidental modifications of objects or arrays and making it easier to trace bugs.

Utilize readonly to enforce immutability where appropriate.

      const data: readonly number[] = [1, 2, 3];
    

9. Neglecting Custom Type Guards

Reliance on implicit checks instead of custom type guards can lead to missed types during runtime, resulting in potential errors.

Implement custom type guards to explicitly check types and make your code more reliable.

function isUser(user: any): user is User {
  return typeof user.email === "string";
}
    

10. Defaulting to any for Unknown Types

Developers often default to using any for variables when the type is initially unknown. This disables type checking, defeating the purpose of using TypeScript.

Use the unknown type for variables when you are not sure about the type initially, and narrow the type as needed.

let input: unknown;

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

Conclusion

By breaking these bad TypeScript habits in 2024, you can write more maintainable, performant, and error-free code. Embracing stricter typing, avoiding shortcuts like any, and fully leveraging TypeScript's advanced features will significantly improve your code quality and make you a more effective developer.

0 comments:

Post a Comment