Как перестать писать Any и полюбить магию типов в TypeScript
Знакомая ситуация: вы описываете интерфейс для API-ответа, и вдруг понимаете, что вам нужно то же самое, но чтобы все поля были обязательными, а одно конкретное — удалено. Или вам нужно сделать объект глубоко неизменяемым (deep readonly), а стандартный Readonly<T> из TypeScript работает только на первом уровне вложенности. В такие моменты начинаешь чувствовать, что встроенных Utility Types катастрофически не хватает.
Обычно разработчики либо начинают городить свои «велосипеды» с использованием infer и рекурсии, либо сдаются и пишут any, обещая себе вернуться и поправить это позже (спойлер: нет). Но есть решение изящнее.
Речь пойдет о проекте Type-fest от Синдре Сорхуса. Это огромная коллекция «сахарных» типов, которые, по-хорошему, должны быть в TypeScript из коробки.
Что это такое и зачем оно в вашем package.json
Если коротко, это библиотека типов. В ней нет исполняемого JavaScript-кода, она не весит ничего в бандле и нужна только на этапе разработки. Синдре Сорхус, автор сотен популярных npm-пакетов, собрал здесь всё то, что ему самому приходилось копипастить из проекта в проект.
Type-fest решает одну простую задачу: делает систему типов TypeScript по-настоящему гибкой. Вместо того чтобы тратить полчаса на написание сложного типа, который вытаскивает ключи из объединения объектов, вы просто импортируете готовое решение.
Четыре типа, которые спасают рабочий день
В библиотеке десятки утилит, но я выделил те, которые пригождаются почти в каждом проекте.
Глубокая модификация объектов
Стандартные Partial<T> или Readonly<T> работают поверхностно. Если внутри объекта есть другой объект, он останется нетронутым. В Type-fest есть PartialDeep, ReadonlyDeep и RequiredDeep.
import type {ReadonlyDeep} from 'type-fest';
interface UserConfig {
api: {
endpoint: string;
timeout: number;
};
}
const config: ReadonlyDeep<UserConfig> = {
api: { endpoint: 'https://api.com', timeout: 5000 }
};
// TypeScript выдаст ошибку: свойство endpoint только для чтения
config.api.endpoint = 'https://other.com';
Работа с JSON
Часто мы пишем any для данных, которые приходят из JSON.parse(). Тип JsonValue из библиотеки строго описывает всё, что может быть валидным JSON: строки, числа, булевы значения, null, массивы и объекты из этих же типов. А Jsonify поможет превратить сложный класс или объект с методами в то, как он будет выглядеть после JSON.stringify().
Умное объединение типов
Тип Merge<A, B> позволяет объединить два интерфейса так, чтобы поля из второго перекрывали поля из первого. Это гораздо удобнее, чем пытаться использовать пересечение &, которое часто ведет к конфликтам типов.
Манипуляции со строками на уровне типов
TypeScript умеет работать с шаблонными литералами, но Type-fest выжимает из этого максимум. Нужно превратить все ключи объекта из snake_case в camelCase? Для этого есть CamelCasedPropertiesDeep. Это незаменимо, когда бэкенд отдает данные в одном стиле, а во фронтенде принят другой.
import type {CamelCasedProperties} from 'type-fest';
interface RawUser {
first_name: string;
last_name: string;
}
type User = CamelCasedProperties<RawUser>;
// Результат: { firstName: string; lastName: string; }
Техническая сторона вопроса
Библиотека требует TypeScript версии 5.9 или выше и включенного режима strict: true в вашем tsconfig.json. Это логично: современные манипуляции с типами опираются на фичи последних версий языка.
Интересно, что авторы не заставляют вас тащить весь пакет в зависимости. Если вам нужен всего один тип, в README прямо сказано: «Просто скопируйте нужный код к себе». Лицензия MIT это позволяет. Но на практике удобнее держать пакет в devDependencies, чтобы получать обновления и исправления багов.
Когда стоит использовать Type-fest
Я часто вижу этот пакет в крупных open-source проектах. Он идеально подходит, если вы:
- Пишете библиотеку и хотите, чтобы у пользователей были идеальные автодополнения в IDE.
- Работаете со сложными структурами данных, которые часто трансформируются.
- Устали от бесконечных
Omit<T, 'key'> & { key: NewType }.
Проект живой, у него огромное сообщество (более 17 тысяч звезд на GitHub) и очень строгий отбор новых типов. Синдре часто отклоняет предложения, если они решают слишком специфичные задачи или если их можно реализовать парой строк стандартного кода. Это гарантирует, что библиотека не превратится в свалку всего подряд.
Type-fest — это не тот инструмент, который меняет архитектуру приложения, но это тот инструмент, который делает код чище, а разработку — приятнее. Он избавляет от необходимости гуглить «typescript how to pick keys by value type» и позволяет сфокусироваться на логике приложения, а не на борьбе с компилятором.
Если вы до сих пор используете Record<string, any> для описания объектов, попробуйте заменить его на UnknownRecord или JsonValue из этой библиотеки. Это маленький шаг, с которого начинается по-настоящему типобезопасный код.
