The title of this article series might offend you, but most of the developers that actually use TypeScript don't know it well enough to avoid the dreaded any type. Chances are that you know the basics of TypeScript — defining types, and, to be fair, in essence that is exactly what TypeScript does, it lets you define types, but types aren't always that simple, especially in the web.

In this article we will see some common mistakes newbies make when using TypeScript.

1. Using the any type

When you don't know what type to use, or you just want to silence the compiler, the easiest thing to do is use any. That basically tells the compiler to shut up and trust you. I don't trust the developers that use the any type, and neither should the compiler. When you silence the compiler, you are welcoming bugs, since type checking is effectively cancelled for that variable.

When using any you are basically writing JavaScript, not TypeScript.

2. Not using the unknown type

When you do not know the type, and feel this impending urge to use any, use unknown instead. It is basically the any type but without silencing the compiler.

Let's see what I mean by that.

None

In the above example, param is of type any. As stated above, this silences the compiler, that means that I can do anything with it and TypeScript won't mind. Like setting the property of param.test. But param can also be undefined, null or the test property might not exist and the compiler still won't care. Let's see how unknown works.

None

Above, because we typed is as unknown, because we literally don't know what the type of param is going to be, TypeScript doesn't let us do whatever we want, it basically doesn't let us alter param in any way as long as it is of type unknown. This is where you should apply type checking logic.

3. Using classes instead of interfaces and types

You might not know, but types and interfaces are not compiled into anything by TypeScript. They are present only at dev time, a class however, is compiled into a function, so don't ever use classes for DTOs.

4. Not using strict mode

Not using strict mode will give you A LOT of cannot set property 'x' of undefined, to say the least. So do yourself a favor and use strict mode, don't be lazy. Let's see what strict mode means:

  • use any in any way (pun intended), as a type, parameter type, generic type, function return type.
  • use non exact match function types — so when typing a function you are no longer allowed to use subtypes or inherited types, the type must match exactly. This means that if you use a function type that has a return type of number, for example, you cannot type a function that returns number | string to it; in non-strict mode this would have been possible, but unsafe, since your function returns an intersection type, while your function type only number.
  • it doesn't let you have uninitialized properties in your classes. This means that you have to initialize all your class properties in the constructor, given them default values or type them as nullable or optional.
  • it doesn't allow the use of catch variables as any, so the catch variable will basically be the unknown type. You cannot use the catch variable until you have done type checking on it.

5. Writing duplicated types.

Most of the developers that use TypeScript don't use utility types from TypeScript, like Omit<T>, Partial<T>, Pick<T> and so on. Hell, most of the developers don't even use type unions and intersections, which is insane!

Here's a simple example to see what I mean by duplicated types.

None

Above, we have a User interface, and then a PatchUserModel, bot identical, however all the options in PatchUserModel are optional. Now, if we were to change the User model in any way, we would have to chnge the PatchUserModel to reflect the User model. This means we have a duplicated type since changing one determines a change in the other. If you use the Partial<T> utility type you can effectively have a single source of truth, the User model, and any change done on the User model will be reflected on the PatchUserModel.

None

Above, PatchUserModel is a perfect mirror of the User type but all the properties are optional. Great! This is just the most basic of usages, I have a separate article which focuses on Utility Types and using Union Types, Intersection Typesand discriminating unions.

6. Not typing functions and using the Function type

This might not seem like much, but it is. Lets look at an example.

None

Above, you won't get any TypeScript errors. You won't get any errors even if you pass a function that doesn't accept an argument with the type of User[]. And thats where the proplem is. There is no way to tell what the signature of the callback should be in the selectUsers method. Here's how it should look.

None

Now the developer calling selectUsers will know what the callback function signature is.

Note: SelectUsersCallbackFn returns unknown since the selectUsers function doesn't know any implementation details about the callback function, it just needs to pass it a User[] as the first parameter, but the return type is unknown to selectUsers. This is one of those cases where you would be tempted to use any, however, unknown is the type safe version of any and that you should use it in this case.

7. Not using generic types

Yes, TypeScript has generics, even better generics that other OOP languages like C# and Java. More complex usages on generics that later in the series.

None

8. Not using Mapped Types.

In TypeScript you can define and interface or type to be an indexed key object. This means that you don't have to know the property name when creating a type. Here's a very very basic example of how to define an interface or a type which has string keys and boolean values.

None

In the above example, you would get an error telling you that the value of thirdItem is incorrect as it should be a boolean.

Conclusion

These are just a few of the mistakes that people make, or features that people don't know exist or that they use wrong. These are not code style mistakes, even though some of them can be caught using a linter and tsconfig flags. But thats a subject for another article.

Thanks for reading!