Types-spring library overview

Types-spring — это package, which improves typescript's built-in types. A kind of add-on over it, improving security and ease of use.

How it works?

We know that TypeScript is a fairly flexible type language that runs on top of JavaScript. And since JavaScript is a weakly dynamically typed language that can be twisted however you like, the types should be so flexible that they could be easily adapted from project to project to the appropriate code base. And TypeScript allows you to do this: it can extend interfaces of the same name (declaration merging) as much as possible without merging conflicts.

The second important point is that TypeScript is not perfect. Despite all its thoughtfulness, it has many problems and shortcomings (we will look at them in the examples below), which have been discussed in the community for many years, and a thousand green issues have been hanging on github for years. Types-spring solves some of these problems. This is a kind of patch over TypeScript that extends the built-in types, making them more precise and useful for the developer.

So let's see what it offers:

From words to deeds

What improvements does types-spring offer us:

Array.map

According to the default behavior of map, when applied to a tuple type (a fixed-length array with positionally typed elements), returns the type of a regular array with indefinite length:

const a = (1, 2, 3) as const; // (number, number, number)
let arr = a.map(r => r + ”) // string()

But where does the length go? After all, according to the documentation, map simply returns a new array, the elements of which are the results of the callback function applied to the elements of the original array. The length does not change. With types-spring it looks more accurate:

const a = (1, 2, 3) as const; // (number, number, number)
let arr = a.map(r => r + ”) // (string, string, string)

Array.isArray

This is a pretty old bug. Issue it has been posted since 2017 and is still not closed. Example:

function checkArray(a: { a: 1 } | ReadonlyArray)
{
if (Array.isArray(a)) { // now a has `any` type
a.forEach(item => item.f()) // => so any runtime error is possible!
}
else { a.a } // type error: property `a` does not exists – but it’s wrong’
}

It is easy to see that type guard does not work correctly in this case. With types-spring, this example will work correctly: variable a, after checking isArray, will have a readonly number() type and will not allow you to shoot yourself in the foot in runtime:

function checkArray(a: { a: 1 } | ReadonlyArray)
{
if (Array.isArray(a)) {
a.forEach(item => item.f()) // type error: f does not exist on type number
}
else { a.a } // success
}

Object.create

It's surprising why the developers decided to cover the create method with type any:

const r = Object.create({}) // r is any

According to the specification, even if we pass null as a prototype, we will still receive an object at the output, only without a prototype – we will not receive either number, boolean, or anything else, in principle. Also, any typescript developer knows that any is a very bad type that should be avoided: it can take any form and is most often just a temporary placeholder. Therefore, with types-spring it works like this:

const r = Object.create({}) // r is object

These are not all the improvements that types-spring provides. It also improves types for interacting with the DOM. More details can be found in readme. In this review, we will look at the additional features that types-spring offers in addition to the built-in types patch, namely utility types.

Utility types

We know what typescript comes with utility types for quick type construction. However, we often encounter situations where the built-in generic types are not enough. And types-spring comes with some additional utility types, which can be quite useful in everyday development. Let's look at a couple of them:

OptionalExceptOne

Let's say we have a function that takes an object representing some options as an argument. Moreover, let’s imagine that we need at least one field from this object to be filled in – it doesn’t matter which one. We can use the OptionalExceptOne generic type:

import type { OptionalExceptOne } from “types-spring”

type Options = OptionalExceptOne<{ phone: number, email: string, telegram: string }>
function func(o: Options) {}
func({ phone: 88888888 })
func({ email: ”rr@mail.ru” })
func( { telegram: ‘@aa’ })
func({ phone: 8888, email: ‘rr@mail.ru’, telegram: ‘@aa’ })

//@ts-expect-error
func({})
//@ts-expect-error
func({ ema: ‘rr@mail.ru’ }) // wrong key

ReduceBy

How often do you use reduce to compress an array into an object? As a rule, it has to be typed manually. However, often this compression occurs on one specific field, and in this case the ReduceBy type comes to the rescue:

type Settings = (
{ infoName: ‘user’, friendsCount: number },
{ infoName: ‘chat’, messagesCount: number }
);
// …
const settings: Settings = (
{ infoName: ‘user’, friendsCount: 2 },
{ infoName: ‘chat’, messagesCount: 5 }
);
const rr = settings.reduce((acc, a) => ({ (a.infoName): a, …acc }), {}) as ReduceBy

/* const rr:
{
user: {
infoName: ‘user’;
friendsCount: number;
};
chat: {
infoName: ‘chat’;
messagesCount: number;
};
}
*/

As input, it takes the tuple itself with objects, and the second argument is one common field for all objects of this tuple, by which the object will be grouped.

The package contains more than 20 similar utilities. All of them can be found in a special readme.

If you found the review useful, please bookmark it, and to be sure, give the repository a star on github. Then at any time you can find it in your Stars tab)

Have a nice day, everyone

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *