Avoiding any in TypeScript: Advanced types and practical uses

Tomasz Świstak
Jan 16, 2019
2
min read

Master TypeScript’s advanced types like Record, Pick, and unknown to write safer, maintainable code while avoiding the pitfalls of any.

TypeScript gives JavaScript developers an opportunity to provide strict types in a code. However, due to the nature of JavaScript, in some cases providing accurate types isn’t a simple task. In such situations programmers are tempted to use any – a typing picklock which allows the user to store anything in it. In this article I’d like to show some built-in types and built-in TypeScript features which you can use to avoid any or to simplify some custom typings.

JAVASCRIPT OBJECTS

Probably, nearly every JavaScript developer at some time has used an object as a map-like collection. However, with strict types it may not be that obvious how to type this (especially with the no-string-literal TSLint rule). The first go-to option is any, but we are losing information of value type. So, we may strictly type an interface with certain keys – but this way we can’t add anything to the object. Another option is Object or {}, but this means “empty object,” so we can’t set anything in it. So, what’s the standard way?

Record

Let’s see the definition:

type Record<K extends keyof any, T> = {
[P in K]: T;
};

Listing 1 Record definition from lib.es5.ts

And the usage:

const dict: Record<string, number> = {};
dict.a = 1;
dict.b = 'a'; // ERROR: "a" is not assignable to type number

Listing 2 Example of using Record type

As you can see, it means that the developer can enter any key, but the value has to be of a specific type. What’s more, we can combine it with other types, therefore making dictionaries with some pre-defined keys (of even different value types!) visible by IntelliSense, e.g.:

const dict2: { a?: string } & Record<string, number> = {};
dict2.a = 'a';
dict2.a = 1; // ERROR: 1 is not assignable to type string
dict2.b = 2;
dict2.b = 'b'; // ERROR: "b" is not assignable to type number

Listing 3 More advanced example of using Record type

I don’t think I need to write about the practical usage of this. Of course, we have a built-in ES6 collection called “Map” that offers a much better experience for dictionaries, but according to benchmarks, when we only want to set and get values, it’s faster to use objects (Record).

MASS MODIFIERS OF OTHER TYPE’S PROPERTIES

What to do, when we already have a type with defined properties, but we don’t like how it is defined? Maybe we want all properties to be optional, required, or read only? TypeScript also has it covered with the types PartialRequired and Readonly. Let’s see their definitions:

type Partial<T> = {
[P in keyof T]?: T[P];
};
type Required<T> = {
[P in keyof T]-?: T[P];
};
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};

Listing 4 Partial, Required and Readonly definitions from lib.es5.ts

Of course, as the article’s title tells us, only Partial can really be used in place of any, but all three are in a way linked to each other, so it would be a waste to not to write about them together.

They offer some quite practical usages. Readonly is very common in typings of React – in components there are some things that you can’t just edit, but you are the person who defines types like a component’s state or properties.

Partial is used wherever you want to offer the possibility to not provide the whole object. A popular usage example is providing the possibility to set the values of an object through one function (or constructor), rather than by setting properties separately. The following listing gives an example of this kind of usage.

class Settings {
setting1: boolean = true;
setting2: string = '';
setting3: number = 1;
constructor(obj: Partial<Settings>) {
Object.assign(this, obj);
}
}
const obj = new Settings({ setting3: 2 });

Listing 5 Example of using Partial

Required is the opposite of Partial. However, here the usages are not so straightforward, since it’s checking if everything is set just at the types level during compilation. Since I’ve never seen any good, practical usages, I won’t provide any. If you have some, please let me know.

PICKING AND OMITTING SPECIFIC A PART OF A TYPE

Earlier we were discussing Partial, which allowed us to make everything in a type optional. That said, we may also want to take a specific part of an object without changing it to be optional (which makes typing even more strict). Let me introduce Pick:

type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};

Listing 6 Pick definition from lib.es5.ts

Pick works like this: it takes only those specific properties that are in the second type. Here’s an example of how it works:

type C = { a: string; b: string; c: boolean; d: string; }
type D = { b: string; c: boolean; }
type pickCkeys = Pick<C, 'c'>
type pickCD = Pick<C, keyof D>;
const c: pickCkeys = { c: true }; // a, b, d doesn't exist
const d: pickCD = { b: 'a', c: true }; // a, d doesn't exist

Listing 7 Usage of Pick

Pick may not seem very useful, since in fact it only takes the given keys or keys from the second type. So you might ask: why don’t we just use this second type or build a type only from those keys? The reason is this – we keep information about the original type. And why not Partial? The difference is when you turn on strictNullChecks flag in compiler settings. Let’s review an example:

type E = { a: string; b: string; }
function withPick<K extends keyof E>(state: Pick<E, K>): void { return; }
function withPartial<E>(state: Partial<E>) { return; }
withPick({ a: null }); // 'null' is not assignable to type string
withPartial({ a: null }); // no error

Listing 8 The difference between Pick and Partial

As you can see, Pick doesn’t allow setting a null value (also undefined) as was defined in the original type. Partial just makes everything nullable, so it does interrupt the original typing. In practice it’s used, for example, in React, in the setState function.

That said, I’ve mentioned omitting. We don’t have a built-in type for that, but we can build one for ourselves like this:

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>

Listing 9 Definition of Omit type

In this way, we can perform a following thing:

type C = { a: string; b: string; c: boolean; d: string; }
type D = { b: string; c: boolean; }
type ComitKeys = Omit<C, 'c'>;
type ComitD = Omit<C, keyof D>;
const e: ComitKeys = { a: 'b', b: 'b', d: 'd' }; // c doesn't exist
const f: ComitD = { a: 'b', d: 'd' }; // b, c doesn't exist

Listing 10 Example of using Omit

If you compare objects e and f from this listing with c and d from listing 7, you can see that we get the complete opposite thing. An example of using Omit can be seen in Ramda to describe the result of a function called omit (quite a surprise, huh?).

UNKNOWN AND ADVANCED TUPLE TYPES

Both TypeScript features which I describe briefly in this part of the article were introduced in TypeScript 3.0, and I’ve already covered them in my article about TypeScript 3.0.

unknown is what should be used when we don’t know a proper type of object. Unlike any, it doesn’t let you do any operations on a value until we know its type (through proper type checks).

Through advanced tuple types I mean mostly extracting and spreading function arguments with tuple types, which was introduced in TypeScript 3.0. Thanks to them, we can define specific types for specific values in an array. While at first glance it doesn’t sound very helpful, they can be useful in typing functional programming features like compose.

I encourage you to review the article I’ve linked above for more detailed descriptions, along with usage examples, so you can have a better grasp of them.

SUMMARY

TypeScript has a lot of typing weaponry ready to be used to keep static types in the dynamically typed environment of JavaScript. The whole problem is that it’s hidden and its usage is not always so straightforward. However, while this article hasn’t described everything that TypeScript offers, I hope it will help you in writing code which will be typed better.

This post was also published on ITNEXT.

Do you need more technical insights? Check out my other articles!

Contact details
By sending a message you allow Synergia Pro Sp. z o.o., with its registered office in Poland, Wroclaw (51-607) Czackiego Street 71, to process your personal data provided by you in the contact form for the purpose of contacting you and providing you with the information you requested. You can withdraw your consent at any time. For more information on data processing and the data controller please refer to our Privacy policy.
*Required
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Tomasz Świstak
JavaScript Developer at Synergy Codes

He’s primarily working with React and NestJS but is open to new frameworks and libraries, whatever suits the project the best. Tomasz is also keen on algorithmic and researching optimal ways to solve complex problems, despite his expertise in Web applications. Implementing complex features and applying scientific theory practically is his main area of interest. 

Get more from me on:
Share:

Articles you might be interested in

The ultimate guide to optimize React Flow project performance [E-BOOK]

Follow these step-by-step process to enhance your React Flow project's performance.

Łukasz Jaźwa
Jan 23, 2025

10 tips for better initial configuration of full-stack apps

Follow these tips for configuring full-stack apps, from using a monorepo with Nx to setting up code formatting and CI/CD pipelines, ensuring smooth development.

Tomasz Świstak
Aug 12, 2022

Effective front-end development with GoJS

Learn how GoJS enhances front-end development by creating interactive diagrams with flexibility and performance for complex data visualizations.

Content team
Oct 1, 2021