Top 16 TypeScript Mistakes Developers Make (And How to Fix Them)

Leapcell: The Best of Serverless Web Hosting TypeScript Development Pitfall Avoidance Guide: Common Issues and Solutions In today's front-end development field, TypeScript has already become the standard for project development. It introduces a powerful type system to JavaScript, greatly enhancing the security and maintainability of the code. However, due to the native weakly typed nature of JavaScript, many developers have a misunderstanding of the type system, and it is easy to fall into various misunderstandings when using TypeScript. This article will summarize the most common development pitfalls and, combined with practical cases, help you write better TypeScript code. I. Issues Related to Type Declaration 1. Abuse of the any Type The any type turns off TypeScript's type checking mechanism, making the type system ineffective. In actual development, try to avoid using any and instead use unknown or explicit type definitions. // Incorrect example: Turns off type checking and is prone to errors at runtime function parseData(data: any) { return data.user.name.toUpperCase(); } parseData(null); // Throws an error at runtime! // Correct example: Use an interface to define the data structure clearly interface User { name: string; } interface Data { user: User; } function parseData(data: Data): string { return data.user.name.toUpperCase(); } 2. Not Declaring the Function Return Type Although TypeScript has the ability of type inference, in complex logic, explicitly declaring the return type can significantly enhance code readability and type safety, especially for public functions and library functions. // Incorrect example: The return type is not clear function getUser(id: number) { if (id === 1) return 'admin'; return null; } // Correct example: Explicitly declare the return type function getUser(id: number): string | null { if (id === 1) return 'admin'; return null; } 3. Irregular Definition of interface and type interface and type are important ways to define types in TypeScript, but using them randomly will make the code difficult to reuse and maintain. It is recommended to use interface to define the object structure and type for type combination or the application of utility types. // Incorrect example: Duplicate definitions lead to conflicts type User = { name: string; }; interface User { age: number; } // Correct example: Use interface uniformly to define the object interface User { name: string; age: number; } II. Type Usage and Conversion Issues 4. Overusing Type Assertions Type assertions are used to bypass the compiler's type checking, but overusing them will undermine the security of type inference. Use them only in special scenarios where the type is clearly known, and give priority to type declarations, interfaces, or generics. // Incorrect example: Abuse of type assertions leads to type insecurity const data = fetchData() as any; const name = (data as { user: { name: string } }).user.name; // Correct example: Use an interface to define the type clearly interface UserData { user: { name: string; }; } const data: UserData = fetchData(); const name = data.user.name; 5. Ignoring the Application of Utility Types TypeScript provides a rich set of built-in utility types (such as Partial, Pick, Omit, etc.). Using these utility types reasonably can simplify the code and improve reusability. // Incorrect example: Redefining type fields interface User { id: number; name: string; age: number; } type UserPreview = { id: number; name: string; }; // Correct example: Use the Pick utility type type UserPreview = Pick; 6. Forcing Assertions When Types Don't Match Type assertions are only used to tell the compiler the type and will not perform actual type conversion. For scenarios where type conversion is required, use safe type conversion methods. // Incorrect example: Incorrect use of assertions for type conversion const val = '123' as unknown as number; // Correct example: Use the Number function for type conversion const val = Number('123'); III. Code Structure and Best Practice Issues 7. Not Using Enums to Manage Constants Magic strings are difficult to maintain and error-prone in the code. Enums should be used to manage constant values uniformly to improve the readability and maintainability of the code. // Incorrect example: Using magic strings function getRole(role: string) { if (role === 'admin') return 'administration'; } // Correct example: Using enums to manage constants enum Role { Admin = 'admin', User = 'user', } function getRole(role: Role) { if (role === Role.Admin) return 'administration'; } 8. Not Using Generics to Abstract Duplicated Code When there is duplicate logic in multiple functions or interfaces, use generics for abstraction to enha

Apr 23, 2025 - 05:39
 0
Top 16 TypeScript Mistakes Developers Make (And How to Fix Them)

Image description

Leapcell: The Best of Serverless Web Hosting

TypeScript Development Pitfall Avoidance Guide: Common Issues and Solutions

In today's front-end development field, TypeScript has already become the standard for project development. It introduces a powerful type system to JavaScript, greatly enhancing the security and maintainability of the code. However, due to the native weakly typed nature of JavaScript, many developers have a misunderstanding of the type system, and it is easy to fall into various misunderstandings when using TypeScript. This article will summarize the most common development pitfalls and, combined with practical cases, help you write better TypeScript code.

I. Issues Related to Type Declaration

1. Abuse of the any Type

The any type turns off TypeScript's type checking mechanism, making the type system ineffective. In actual development, try to avoid using any and instead use unknown or explicit type definitions.

// Incorrect example: Turns off type checking and is prone to errors at runtime
function parseData(data: any) {
  return data.user.name.toUpperCase();
}
parseData(null); // Throws an error at runtime!

// Correct example: Use an interface to define the data structure clearly
interface User {
  name: string;
}
interface Data {
  user: User;
}
function parseData(data: Data): string {
  return data.user.name.toUpperCase();
}

2. Not Declaring the Function Return Type

Although TypeScript has the ability of type inference, in complex logic, explicitly declaring the return type can significantly enhance code readability and type safety, especially for public functions and library functions.

// Incorrect example: The return type is not clear
function getUser(id: number) {
  if (id === 1) return 'admin';
  return null;
}

// Correct example: Explicitly declare the return type
function getUser(id: number): string | null {
  if (id === 1) return 'admin';
  return null;
}

3. Irregular Definition of interface and type

interface and type are important ways to define types in TypeScript, but using them randomly will make the code difficult to reuse and maintain. It is recommended to use interface to define the object structure and type for type combination or the application of utility types.

// Incorrect example: Duplicate definitions lead to conflicts
type User = {
  name: string;
};
interface User {
  age: number;
}

// Correct example: Use interface uniformly to define the object
interface User {
  name: string;
  age: number;
}

II. Type Usage and Conversion Issues

4. Overusing Type Assertions

Type assertions are used to bypass the compiler's type checking, but overusing them will undermine the security of type inference. Use them only in special scenarios where the type is clearly known, and give priority to type declarations, interfaces, or generics.

// Incorrect example: Abuse of type assertions leads to type insecurity
const data = fetchData() as any;
const name = (data as { user: { name: string } }).user.name;

// Correct example: Use an interface to define the type clearly
interface UserData {
  user: {
    name: string;
  };
}
const data: UserData = fetchData();
const name = data.user.name;

5. Ignoring the Application of Utility Types

TypeScript provides a rich set of built-in utility types (such as Partial, Pick, Omit, etc.). Using these utility types reasonably can simplify the code and improve reusability.

// Incorrect example: Redefining type fields
interface User {
  id: number;
  name: string;
  age: number;
}
type UserPreview = {
  id: number;
  name: string;
};

// Correct example: Use the Pick utility type
type UserPreview = Pick<User, 'id' | 'name'>;

6. Forcing Assertions When Types Don't Match

Type assertions are only used to tell the compiler the type and will not perform actual type conversion. For scenarios where type conversion is required, use safe type conversion methods.

// Incorrect example: Incorrect use of assertions for type conversion
const val = '123' as unknown as number;

// Correct example: Use the Number function for type conversion
const val = Number('123'); 

III. Code Structure and Best Practice Issues

7. Not Using Enums to Manage Constants

Magic strings are difficult to maintain and error-prone in the code. Enums should be used to manage constant values uniformly to improve the readability and maintainability of the code.

// Incorrect example: Using magic strings
function getRole(role: string) {
  if (role === 'admin') return 'administration';
}

// Correct example: Using enums to manage constants
enum Role {
  Admin = 'admin',
  User = 'user',
}
function getRole(role: Role) {
  if (role === Role.Admin) return 'administration';
}

8. Not Using Generics to Abstract Duplicated Code

When there is duplicate logic in multiple functions or interfaces, use generics for abstraction to enhance the extensibility and reusability of the code.

// Incorrect example: Repeating the implementation of similar functions
function wrapString(value: string): string[] {
  return [value];
}
function wrapNumber(value: number): number[] {
  return [value];
}

// Correct example: Using generics to implement general logic
function wrap<T>(value: T): T[] {
  return [value];
}

9. Not Enabling the strict Mode

The strict mode is the core of TypeScript's type checking. Turning it off will cause many potential problems to be ignored. It is recommended to enable the strict mode in tsconfig.json.

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

IV. Runtime and Detail Issues

10. Ignoring IDE Hints

IDEs such as VSCode can highlight potential type issues. Developers should pay attention to these hints and fix the type errors in the code in a timely manner.

// Incorrect example: Ignoring the type error hinted by the IDE
const name: string = 123; // Type mismatch, should be fixed

11. Not Using Type Narrowing

Type narrowing allows TypeScript to automatically narrow the variable type range based on conditional judgments. By reasonably using operators such as typeof, in, and instanceof, runtime errors can be avoided.

// Incorrect example: Not handling the null case
function printLength(str: string | null) {
  return str.length; // Throws an error
}

// Correct example: Using type narrowing
function printLength(str: string | null) {
  if (str) {
    return str.length;
  }
  return 0;
}

12. Not Handling null and undefined

TypeScript does not force the handling of null and undefined by default. It is recommended to enable the strictNullChecks option and explicitly handle null values in the code.

// Incorrect example: Not considering the case where name is undefined
function greet(name: string) {
  return 'Hello ' + name.toUpperCase(); 
}

// Correct example: Using optional parameters and handling null values
function greet(name?: string) {
  return name ? 'Hello ' + name.toUpperCase() : 'Hello';
}

13. Accessing Object Properties Without Null Checks

When accessing nested object properties, be sure to use the optional chaining operator (?.) or the nullish coalescing operator (??) for null checks to prevent runtime errors.

// Incorrect example: Not checking if user exists
const username = user.profile.name;

// Correct example: Using optional chaining and nullish coalescing
const username = user?.profile?.name ?? 'Anonymous';

V. Other Common Issues

14. Not Explicitly Specifying Generic Parameters

When using generics, explicitly pass in type parameters to avoid potential problems caused by unclear type inference.

// Incorrect example: The type is not clear
const arr = Array(); // The type is any[]

// Correct example: Explicitly specify the type
const arr: Array<number> = [];

15. Mixing == and ===

== will perform implicit type conversion, which may lead to unexpected results. In TypeScript, always use the strict equality operator === for comparison.

// Incorrect example: Using == may cause ambiguity
if (value == null) {
  // May match both undefined and null
}

// Correct example: Using === for a clear judgment
if (value === null) {
  // Only matches null
}

Conclusion

The key to mastering TypeScript lies in a deep understanding of the design concept of the type system and developing a standardized coding habit. By avoiding the above 16 common issues and reasonably using features such as type declarations, utility types, and generics, you will be able to write more secure and maintainable code and fully leverage the advantages of TypeScript.

Leapcell: The Best of Serverless Web Hosting

Finally, I recommend a platform that is most suitable for deploying nodejs services: Leapcell

Image description