DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Newsletter
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Build Your Own Shopping Cart With React Hooks
  • Mule 4 DataWeave(1.x) Script To Resolve Wildcard Dynamically
  • Adding Two Hours in DataWeave: Mule 4
  • How Go Maps Work: From Buckets to Swiss Tables

Trending

  • Fix the Target, Precompute Once: A Backend-Free Word-Ladder Solver With a BFS Distance Field
  • OpenCode: Setting Up an Open-Source AI Coding Assistant
  • AI Agents and Frontier Models: Insights From The Neuron
  • Why Vulnerability Management Fails Without Reliable Asset Data

Draft This article hasn't been published, so readers can't see it.

In moderation This article is waiting for review, so readers can't see it yet.

Deleted This article has been deleted, so readers can't see it.

  1. DZone
  2. Data Engineering
  3. Data
  4. TypeScript: Useful Features

TypeScript: Useful Features

Some advanced constructs may require a learning curve, but can significantly bolster your type safety. This article introduces you to some of these advanced features.

By 
Vasyl Mysiura
·
Sep. 22, 23 · Tutorial
Post on X (opens in a new tab) Share on LinkedIn (opens in a new tab)
7.7K Views

Join the DZone community Save articles, join the discussion, and download Refcards and Trend Reports with a free account.

Join for free Already a member? Log in

Nowadays, it’s difficult to imagine a serious JavaScript-based application without a TypeScript superset. Interfaces, tuples, generics, and other features are well-known among TypeScript developers. While some advanced constructs may require a learning curve, they can significantly bolster your type safety. This article aims to introduce you to some of these advanced features.

Type Guards

Type guards help us to get info about a type within a conditional block. There are a few simple ways to check the type using in, typeof, instanceof operators, or using equality comparison (===).

In this section, I’d like to pay more attention to user-defined type guards. This guard serves as a simple function that returns a boolean value. In other words, the return value is a type predicate.
Let’s take a look at the example when we have base user info and user with additional details:

JavaScript
 
type User = { name: string };
type DetailedUser = { 
  name: string; 
  profile: { 
    birthday: string
  }
}

function isDetailedUser(user: User | DetailedUser) {
  return 'profile' in user;
}

function showDetails(user: User | DetailedUser) {
if (isDetailedUser(user)) {
  console.log(user.profile); // Error: Property 'profile' does not exist on      type 'User | DetailedUser'.
          }
}


The isDetailedUser function returns a boolean value, but it does not identify this function as a boolean that “defines the object type.”

In order to achieve the desired result, we need a little bit of update isDetailedUser function using ““user is DetailedUser” construction

JavaScript
 
function isDetailedUser(user: User | DetailedUser): user is DetailedUser {
  return 'profile' in user;
}


Indexed Access Types

There may be the case in your app when you have a large object type and you want to create a new type, that uses a part of the original one. For example, part of our app requires only a user profile. User[‘profile’] extracts the desired type and assigns it to the UserProfile type.

JavaScript
 
type User = {
  id: string;
  name: string;
  surname: string;
  profile: {
    birthday: string;
  }
}

type UserProfile = User['profile'];


What if we want to create a type based on a few properties? In this case, you can use a built-in type called Pick.

JavaScript
 
type FullName = Pick<User, 'name' | 'surname'>; // { name: string; surname: string }


There are many other utility types, such as Omit, Exclude, and Extract, which may be helpful for your app. At first sight, all of them are kind of indexed types, but actually, they are built on Mapped types.

Indexed Types With an Array

You might have met the case when an app provided you with a union type, such as:

JavaScript
 
type UserRoleType = ‘admin’ | ‘user’ | ‘newcomer’;


Then, in another part of the app, we fetch user data and check its role. For this case, we need to create an array:

JavaScript
 
const ROLES: UserRoleType[] = [‘admin’, ‘user’, ‘newcomer’];
ROLES.includes(response.user_role);


Looks tiring, doesn't it? We need to repeat union-type values inside our array. It would be great to have a feature to retrieve a type from an existing array to avoid duplication. Fortunately, indexed types help here as well.

First of all, we need to declare our array using a const assertion to remove the duplication and make a read-only tuple.

JavaScript
 
const ROLES = [‘admin’, ‘user’, ‘newcomer’] as const;


Then, using the typeof operator and number type, we create a union type based on the array value.

JavaScript
 
type RolesType = typeof ROLES[number]; // ‘admin’ | ‘‘user’ | ‘‘newcomer’;


You may be confused about this solution, but as you may know, arrays are object-based constructions with numeric keys. That’s why, in this example, number is used as the index access type.

Conditional Types and Infer Keyword

Conditional types define a type that depends on the condition. Usually, they are used along with generics. Depending on the generic type (input type), construction chooses the output type.

For example, the built-in NonNullable TypeScript type is built on conditional types.

JavaScript
 
type NonNullable<T> = T extends null | undefined ? never : T
type One = NonNullable<number>; // number
type Two = NonNullable<undefined>; // never


The infer keyword is used with conditional types and can not be used outside of the ‘extends’ clause. It serves as a ‘type variable creator.’

I think it will be easier for you to understand it by looking at the real example. 

Case: retrieve async function result type.

JavaScript
 
const fetchUser = (): Promise<{ name: string }> => { /* implementation */ }


The easiest solution is to import the type declaration and assign it to the variable. Unfortunately, there are cases when result declaration is written inside the function, as in the example above. 

This problem may be resolved in two steps:

  1.  The Awaited utility type was introduced in TypeScript 4.5. For learning purposes, let’s look at the simplified variant.
JavaScript
 
export type Awaited<T> = T extends Promise<infer U> ? U : T;


Using conditional types and infer keyword, we “pull out” the promised type and assign it to the Uname. It’s a kind of type variable declaration. If the passed type is acceptable with PromiseLike generic, construction returns the original type saved to the U name.

2. Get value from the async function.

Using built-in ReturnType that extracts the return type of function and our Awaited type, we achieve the desired result:

JavaScript
 
export type Awaited ReturnType<T> = Awaited<Return Type<T>>;


I hope you found this article useful for yourself. Have fun coding!

Data structure JavaScript TypeScript Data Types

Opinions expressed by DZone contributors are their own.

Related

  • Build Your Own Shopping Cart With React Hooks
  • Mule 4 DataWeave(1.x) Script To Resolve Wildcard Dynamically
  • Adding Two Hours in DataWeave: Mule 4
  • How Go Maps Work: From Buckets to Swiss Tables

Partner Resources

×

Comments

Loading comments

The comments didn't load

Please try again in a moment.

Likes

Loading

The next likes didn't load. Please try again.

The likes didn't load

Please try again in a moment.

See who liked this

Log in or join DZone for free to see who liked this article.

Log in Join for free
  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook