Maximize Your TypeScript Skills: Rarely-Used Features You Need to Know

Maximize Your TypeScript Skills: Rarely-Used Features You Need to Know

Elevate Your Code with These 6 Underutilised TypeScript Features

Dec 18, 2022·

12 min read

Welcome to my comprehensive guide on some of the lesser-known features of TypeScript! 🤗 TypeScript is a powerful typed superset of JavaScript that can help you write more maintainable and error-free code. In this guide, I will be covering a variety of advanced features of TypeScript that you may not be familiar with, including type guards, the "readonly" modifier, the "as const" assertion, the "unique" modifier, the "infer" keyword, and the "never" type. Each of these features will be thoroughly explained and accompanied by practical examples 🧑‍💻 to help you understand how to use them in your own projects. Whether you're just getting started with TypeScript or you're a seasoned pro looking to expand your knowledge, this post has something for everyone.

1️⃣ Type guards

TypeScript is a powerful typed superset of JavaScript that helps you write safer and more maintainable code. One of the features that make TypeScript so powerful is type guards, which allow you to narrow down the type of an object based on a boolean condition. let's take a deep dive into type guards and how they can help you write better TypeScript code.

What are TypeScript Type Guards?

TypeScript type guards are a way to narrow down the type of an object based on a boolean condition. This can be useful in situations where you have an object that could be of multiple types, and you need to handle each type differently.

For example, consider the following code:

function getFullName(person: Person | Employee): string {
  if (person.employeeId) {
    return `${person.firstName} ${person.lastName} (Employee #${person.employeeId})`;
  } else {
    return `${person.firstName} ${person.lastName}`;
  }
}

In this code, the getFullName function takes an object that could be either a Person or an Employee. If the object is an Employee, it should include an employeeId property, which we use to generate a different string than if the object is just a Person.

To handle this situation, we can use a type guard by adding a type-check to the if statement. This will tell TypeScript that the object is of a certain type if the condition is true, allowing us to access properties of that type on the object.

function getFullName(person: Person | Employee): string {
  if (typeof person.employeeId === 'number') {
    return `${person.firstName} ${person.lastName} (Employee #${person.employeeId})`;
  } else {
    return `${person.firstName} ${person.lastName}`;
  }
}

Now, TypeScript will understand that the object is of type Employee if the employeeId property is a number, and it will be of type Person if it is not. This allows us to access the firstName and lastName properties safely, knowing that they will always be present on the object.

Using type guards with the "in" keyword

There are several ways to use type guards in TypeScript. The most common way is to use a type-check in an if the statement, as we saw in the example above. This is called a "user-defined type guard."

Another way to use type guards is with the in keyword. The in keyword can be used to check if a property exists on an object, even if the object is of a type that doesn't have that property.

For example:

function hasEmployeeId(person: Person | Employee): person is Employee {
  return 'employeeId' in person;
}

if (hasEmployeeId(person)) {
  console.log(`Employee #${person.employeeId}`);
}

In this example, the hasEmployeeId function uses the in keyword to check if the employeeId property exists on the person object. If it does, the function returns true.

2️⃣ Readonly Modifier

The "readonly" modifier in TypeScript is a way to make properties of an object read-only, meaning they can only be accessed and not modified. This can be useful for preventing unintended changes to an object, particularly when working with data that should be immutable.

How to Use the Readonly Modifier ?

To use the read-only modifier, you simply need to add the "readonly" keyword before the name of the property you want to make read-only. For example:

class Person {
  readonly firstName: string;
  readonly lastName: string;

  constructor(firstName: string, lastName: string) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}

In this code, the firstName and lastName properties of the Person class are marked as read-only, which means they can only be accessed and not modified.

You can also use the readonly modifier with object literals. For example:

const person = {
  readonly firstName: 'John',
  readonly lastName: 'Doe',
};

In this code, the firstName and lastName properties of the person object are marked as read-only.

Benefits of the Readonly Modifier

Using the readonly modifier has several benefits. First and foremost, it helps to prevent unintended changes to an object. This can be particularly useful when working with data that should be immutable, such as configuration data or data that is being passed between components.

In addition, using the read-only modifier can improve the type safety of your code. By marking a property as read-only, you are telling TypeScript that it should only be accessed and not modified. This can help to catch errors early on in the development process and make your code easier to maintain.

Finally, using the read-only modifier can make your code more self-documenting. By explicitly marking a property as read-only, you are indicating to other developers that the property should not be modified, which can make your code easier to understand and work with.

3️⃣ As Const Assertion

The "as const" assertion in TypeScript is a way to tell the compiler that an object literal should be treated as a "const" object, even if it isn't marked with the "const" keyword. This can be useful for preventing unintended changes to objects that are meant to be immutable.

How to Use the "as const" Assertion ?

To use the "as const" assertion, you simply need to add "as const" after the object literal. For example:

const person = {
  firstName: 'John',
  lastName: 'Doe',
} as const;

In this code, the person object is treated as a "const" object. This means that the properties of the object cannot be modified.

You can also use the "as const" assertion with arrays. For example:

const names = ['John', 'Jane'] as const;

In this code, the names array is treated as a "const" array, meaning that the elements of the array cannot be modified.

Benefits of the "as const" Assertion

Using the "as const" assertion has several benefits. First and foremost, it helps to prevent unintended changes to an object. This can be particularly useful when working with data that should be immutable, such as configuration data or data that is being passed between components.

In addition, using the "as const" assertion can improve the type safety of your code. By telling TypeScript that an object should be treated as a "const" object, you are explicitly indicating that the object should not be modified, which can help to catch errors early on in the development process.

Differences between "as const" keyword and "readonly" modifier

The "readonly" modifier and the "as const" assertion are both features of TypeScript that can be used to prevent unintended changes to objects. However, they work in slightly different ways and are used for different purposes.

The "readonly" modifier is used to make properties of an object read-only, meaning they can only be accessed and not modified. This is typically used when you want to prevent changes to an object that is being shared between multiple parts of your code.

The "as const" assertion, on the other hand, is used to tell TypeScript that an object literal should be treated as a "const" object, even if it isn't actually marked with the "const" keyword. This is typically used when you want to prevent changes to an object that is meant to be immutable, such as configuration data or data that is being passed between components.

One key difference between the "readonly" modifier and the "as const" assertion is that the "readonly" modifier can only be used with properties of a class or object literal, whereas the "as const" assertion can be used with any object literal, including arrays and tuples.

Another difference is that the "readonly" modifier only affects the object itself, whereas the "as const" assertion also affects the values of the object's properties. For example, if an object has a "readonly" property that is an array, the array can still be modified, but if the same object has the same array as a "const" property, the array cannot be modified.

In summary, the "readonly" modifier is used to make properties of an object read-only, while the "as const" assertion is used to tell TypeScript that an object should be treated as a "const" object. Both features can be useful for preventing unintended changes to objects, but they work in slightly different ways and are used for different purposes.

4️⃣ Unique Modifier

The "unique" modifier in TypeScript is a way to create an array type that only allows unique values. This can be useful for ensuring that an array only contains unique values, which can help prevent errors and maintain the integrity of your data.

How to Use the Unique Modifier ?

To use the unique modifier, you simply need to add the "unique" keyword before the type of the array. For example:

const names: unique string[] = ['John', 'Jane', 'John'];

In this code, the names array is of type unique string[], which means it can only contain unique string values. If you try to assign a non-unique value to the array, TypeScript will give you an error.

You can also use the unique modifier with tuple types. For example:

const numbers: unique [number, number, number] = [1, 2, 3];

In this code, the numbers tuple is of type unique [number, number, number], which means it can only contain unique numbers.

Benefits of the Unique Modifier

Using the unique modifier helps to ensure that an array only contains unique values, which can be useful for preventing errors and maintaining the integrity of your data. For example, if you have an array of user IDs that should only contain unique values, using the unique modifier can help to catch any errors that may occur if a duplicate value is added to the array.

In addition, using the unique modifier can improve the type safety of your code. By explicitly specifying that an array should only contain unique values, you are telling TypeScript that it should check for duplicate values and give you an error if it finds any. This can help to catch errors early on in the development process and make your code easier to maintain.

5️⃣ Infer Keyword

The "infer" keyword in TypeScript is a way to extract the type of a value that is being used in a type context. This can be useful for creating more flexible and reusable types, as well as for improving the type inference of your code.

How to Use the Infer Keyword ?

To use the infer keyword, you need to use it in the context of a type alias or type parameter. For example:

type UnwrapArray<T> = T extends (infer U)[] ? U : T;

In this code, the UnwrapArray type alias takes a type T and uses the "infer" keyword to extract the type of element in an array. This allows the type alias to be used to unwrap the element type of an array, regardless of the type of the array.

You can also use the infer keyword with type parameters. For example:

function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

const value = getProperty({ foo: 'bar' }, 'foo');

In this code, the getProperty function takes an object T and a key K that is a subtype of keyof T. The function uses the "infer" keyword to extract the type of the value associated with the key in the object. This allows the function to return the correct type for the value, regardless of the type of object and key.

Benefits of the Infer Keyword

Using the infer keyword has several benefits. First and foremost, it allows you to create more flexible and reusable types. By using the infer keyword in a type alias or type parameter, you can extract the type of a value and use it in a type context, which can be useful for creating more generic types that can be used in a variety of situations.

In addition, using the infer keyword can improve the type inference of your code. By using the infer keyword in a type parameter, you can tell TypeScript to infer the type of a value based on the type of another value, which

6️⃣ Never Type

The "never" type in TypeScript is a type that represents the absence of a value. It is a bottom type, which means it is a subtype of all other types and can never be instantiated.

How to Use the Never Type ?

The never type is typically used in situations where a function or expression is expected to return a value, but it never actually does. For example:

function error(message: string): never {
  throw new Error(message);
}

In this code, the error function is declared to return a value of type never. This is because the function always throws an error, which means it never actually returns a value.

You can also use the never type in the return type of a function that has an infinite loop or never-ending execution. For example:

function infiniteLoop(): never {
  while (true) {
    console.log('Running...');
  }
}

In this code, the infiniteLoop function is declared to return a value of type never, because it has an infinite loop and never actually returns a value.

🧠 To Summarize

  1. TypeScript type guards are a way to narrow the type of an object based on a boolean condition.

  2. The "readonly" modifier is used to make properties of an object read-only, meaning they can only be accessed and not modified.

  3. The "as const" assertion is used to tell TypeScript that an object literal should be treated as a "const" object, even if it isn't actually marked with the "const" keyword.

  4. The "unique" modifier is used to create an array type that only allows unique values.

  5. The "infer" keyword is used to extract the type of a value that is being used in a type context.

  6. The "never" type represents the absence of a value and is a subtype of all other types.

As we've seen, TypeScript is a powerful typed superset of JavaScript that can help you write more maintainable and error-free code. In this guide, we've explored some of the lesser-known features of TypeScript

Keep in mind that this is just a sampling of the many features that TypeScript has to offer, and there are always new updates and features being added to the language. If you have a favourite TypeScript feature that I didn't cover in this post, let me know in the comments! Whether you're just getting started with TypeScript or you're a seasoned pro looking to expand your knowledge, I hope this guide has been helpful and informative.

Did you find this article valuable?

Support Srujan Gurram by becoming a sponsor. Any amount is appreciated!