Написать пост

Обзор библиотеки types-spring

Аватар Alexander

Улучшенные типы и дополнительные утилиты для разработки на typesctipt вместе с types-spring

Обложка поста Обзор библиотеки types-spring

Types-spring — это библиотека надстройка над typescript, улучшающая безопасность и удобство использования встроенных типов.

А для чего?

К сожалению, typeScript не идеален: несмотря на всю его продуманность, он имеет много проблем и недоработок, которые в сообществе годами являются предметами обсуждений , но так и остаются нерешенными. Types-spring ставит перед собой цель нивелировать как можно больше таких недоработок. Это своего рода патч над TypeScript, который расширяет встроенные типы, делая их более точными и полезными для разработчика.

Как это работает?

Мы знаем, что typescript — это довольно гибкий язык [типов], работающий поверх JavaScript. И поскольку JavaScript – язык со слабой динамической типизацией, которой можно крутить как душе угодно, то и типы должны быть настолько гибкими, чтобы их можно было легко адаптировать от проекта к проекту под соответствующую кодовую базу. К счастью, typescript позволяет это сделать: достаточно просто расширить одноименные интерфейсы (declaration merging), избегая конфликтов слияния.

Но предлагаю посмотреть, что же он предлагает:

От слов к делу

Усовершенствованные типы:

Array.map

Сейчас метод .map, примененный к типу кортежа (массиву с фиксированной длинной и позиционно типизированными элементами), возвращает тип обычного массива с неопределенной длиной:

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

Но куда пропадает длина, ведь метод map не меняет длину исходного массива? С types-spring это выглядит точнее:

			import 'types-spring'
const a = [1, 2, 3] as const;
let arr = a.map(r => r + '')  // [string, string, string]
		

Array.isArray

С этим методом связан довольно старый баг. Issue о нем висит еще с 17 года и до сих пор открыта. Пример воспроизведения бага:

			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'
}
		

По дефолту такой type guard работает неправильно. Но types-spring не позволит ошибке протечь в рантайм:

			/// с types-spring:
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

Удивительно, почему разработчики решили покрыть метод create типом any:

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

Cогласно спецификации даже если мы передадим в качестве прототипа null — на выходе все равно получим объект (правда без прототипа) — ни number, ни boolean, ни что либо иное мы не получим, в принципе. Так же любой typescript-разработчик знает, что any – это очень плохой тип, которого нужно избегать: он может принимать любые формы и чаще всего является просто временной заглушкой. Чтобы это исправить включаем types-spring в свой проект:

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

Это беглый обзор пакета и не включает описание всех его возможностей. Более подробно с ними можно ознакомиться в официальном readme. Там же можно найти и улучшения встроенных типов DOM. А здесь мы остановимся на другом удивительном инструменте, поставляемом в том же пакете — типах-утилитах.

Utility types

Мы знаем, что typescript поставляется с типами утилитами для быстрого конструирования типов. Однако зачастую сталкиваемся с ситуациями, когда встроенных типов не хватает. Types-spring поставляет с собой некоторые дополнительные типы-утилиты, которые могут быть весьма полезны повседневной разработке. Рассмотрим парочку из них:

OptionalExceptOne

Допустим у нас есть функция, которая принимает в качестве аргумента объект, представляющий собой некоторые опции. Причем нам необходимо, чтобы как минимум одно поле из этого объекта было передано — не важно какое. Для этого мы можем использовать универсальный тип OptionalExceptOne:

			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({})
		

ReduceBy

Как часто мы используем reduce для сжатия массива до объекта? Как правило, результат приходится типизировать вручную. Однако зачастую такое “сжатие” происходит по какому-то одному конкретному полю. В этом случае на помощь приходит тип ReduceBy:

			import type { ReduceBy } from "types-spring"

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

/* type of `rr` is:
{
    user: {
        infoName: 'user';
        friendsCount: number;
    };
    chat: {
        infoName: 'chat';
        messagesCount: number;
    };
}
*/
		

На вход он принимает сам кортеж с объектами, а вторым аргументом — одно общее поле для всех объектов этого кортежа, по которому будет сгруппирован объект.

Types-spring содержит больше 20-ти подобных утилит. Все их можно найти в специальном readme для утилит.

Если обзор вам показался полезным, добавляйте в закладки, а для уверенности — поставьте репозиторию звезду на github. Тогда в любой момент сможете найти его у себя во вкладке Stars )

Всем хорошего дня!

Следите за новыми постами
Следите за новыми постами по любимым темам
618 открытий2К показов