Typescript Best Practices #1 - Always prefer 'Type Declaration' over 'Type Assertion'

Typescript Best Practices #1 - Always prefer 'Type Declaration' over 'Type Assertion'

In TypeScript, there are two ways of assigning a type to a variable.

  • Type Declaration
  • Type Assertion

If you have ever written typescript code, you most probably would have used both of them at some point in time. So let's dive deep into what type declaration and type assertion are and along the way understand why it is a best practice to always prefer type declaration over type assertion.

Type Declaration

Let's try to understand type declaration with the help of an example

interface IProduct {
  name: string;
  price: number;
}

const product_1: IProduct = {
    name: "MacBook Pro",
    price: 150000
};

Here you are telling the typescript compiler that the variable product_1 should be of type IProduct. Since you have declared a type like this, now the typescript compiler will check if the value in variable product adheres to the type IProduct. If the check fails, the typescript compiler will throw an error.

For example, if you missed adding the price property in the product_1, you will get the below error.

// ~~~ Property 'price' is missing in type '{ name: string; }' but required in type 'IProduct'
const product_1: IProduct = {
    name: "MacBook Pro",
};

Similarly for an empty object as well, typescript will throw below error.

// ~~~ Type '{}' is missing the following properties from type 'IProduct': name, price
const product_1: IProduct = {};

In most cases, this is exactly what you want.

The key point to note here is - In Type Declaration, you are telling the typescript compiler that you want the given variable to be of a specific type and that the typescript compiler should ensure that the value conforms to the said type.

Now let's see how type assertion is different.

Type Assertion

Type assertion also assigns a type to a variable. For example, check out the below example

interface IProduct {
  name: string;
  price: number;
}

const product_1 = {
    name: "MacBook Pro",
    price: 150000
} as IProduct;

Notice how the variable product_1 is assigned a type IProduct using as keyword. This looks pretty similar to Type Declaration, however, there is a really important difference.

Here, instead of asking the compiler to check that the value assigned to the variable adheres to the provided type, you are telling the compiler that the provided value does adhere to the provided `type. In other words, you know about the type of the value of the variable better than the typescript compiler and thus typescript compiler shouldn't second-guess you.

Now, let's go over why type assertion isn't good. For that, let's modify the code to remove the price property from variable product_1

const product_1 = {
    name: "MacBook Pro",
} as IProduct;

Now, even though the price property is missing, because you are using type assertion here, typescript doesn't throw an error.

In fact, typescript would not even throw an error if the value would have been an empty object as below

const product_1 = {} as IProduct;

Further, in the case of type assertion, if the value has additional properties, even then typescript won't complain. For example, let's add an additional property 'color'

interface IProduct {
  name: string;
  price: number;
}

const product_1 = {
    name: "MacBook Pro",
    price: 150000,
    color: 'space grey' // No error
} as IProduct;

If you had defined the type using type declaration, typescript would have thrown an error and prevented a potential bug from getting introduced.

So, as a rule of thumb, in most cases, you should prefer using type declaration over type assertion. Unless you are absolutely sure what you are doing, you should consider type assertions as an anti-pattern.

But then,

Should you ever use a type assertion?

Yes! Sometimes you have to use type assertion. Especially when you actually know more about the type than what the typescript compiler knows. Let's see some example use cases

Example 1: When you use a DOM API to retrieve a DOM element.

document.querySelector('#btn');.addEventListener('click', e => {
  e.currentTarget // Type is EventTarget
  const button = e.currentTarget as HTMLButtonElement; // type assertion
  button // Type is HTMLButtonElement
});

Since Typescript doesn't really know anything about the actual DOM your code is going to access, it doesn't know that document.querySelector('#submitButton') would return a button. But you do know that. So making a type assertion here makes sense.

Example 2: Let's say you fetch data using some 3rd party javascript library. Now Typescript doesn't know anything about the type of data returned by that library. However, you can assert what type of data would be returned.

import fetchProductData from 'some-3rd-party-lib';

const prouductData = fetchProductData() as IProduct; // Type assertion
console.log(prouductData.name); // No error

Conclusion

TypeScript is a very powerful weapon that can help you catch and fix bugs even before you run the code on a browser. In order to truly harness TypeScript's power, as a best practice, you should make sure that you always prefer Type Declaration instead of Type Assertion. Type Declaration would give your code much better type safety.

Thank you for reading, hope you found this useful. Let me know in the comments below.

Happy coding!