Type-fest для TypeScript: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое Type-fest и зачем он нужен?

Type-fest — это коллекция утилитарных типов для TypeScript, созданная сообществом Sindre Sorhus. Эта библиотека предоставляет десятки готовых типов, которые решают повседневные задачи при разработке на TypeScript: от простых преобразований (например, сделать все поля объекта необязательными) до сложных условных типов (например, извлечь тип значения из Promise).

Зачем нужна эта библиотека? TypeScript — мощный язык, но его встроенные типы (Partial, Required, Pick, Record) покрывают лишь базовые сценарии. В реальных проектах часто требуются более специфичные конструкции: глубокое объединение объектов, типы для работы с JSON, строгая типизация констант, извлечение типов из сложных структур и т.д. Type-fest избавляет от необходимости писать эти типы вручную, экономя время и снижая вероятность ошибок.

Библиотека активно поддерживается, имеет более 10 000 звезд на GitHub и используется в таких крупных проектах, как Next.js, Vite и Expo.

Установка

Type-fest — это npm-пакет, который устанавливается как dev-зависимость (так как типы не влияют на рантайм).

# npm
npm install --save-dev type-fest

# yarn
yarn add --dev type-fest

# pnpm
pnpm add --save-dev type-fest

Обратите внимание: библиотека не устанавливается через pip — она предназначена исключительно для TypeScript/JavaScript.

Быстрый старт

После установки вы можете импортировать любой тип из библиотеки. Рассмотрим простейший пример — использование типа PartialDeep, который делает все поля объекта (включая вложенные) необязательными.

import type { PartialDeep } from 'type-fest';

interface User {
  id: number;
  name: string;
  address: {
    city: string;
    zip: number;
  };
}

// Обычный Partial делает необязательными только поля первого уровня
type PartialUser = Partial<User>;
// { id?: number; name?: string; address?: { city: string; zip: number; } }

// PartialDeep делает необязательными все вложенные поля
type DeepPartialUser = PartialDeep<User>;
// { id?: number; name?: string; address?: { city?: string; zip?: number; } }

const user: DeepPartialUser = {
  name: 'Alice',
  address: {
    city: 'Wonderland'
    // zip можно не указывать
  }
};

Как видите, PartialDeep автоматически проходится по всем уровням вложенности — это то, что приходится писать вручную с помощью рекурсивных типов.

Основные методы/типы

Рассмотрим 10 наиболее полезных типов из Type-fest. Для каждого приведено описание, параметры (если есть) и рабочий пример.

1. PartialDeep

Что делает: Делает все свойства объекта (включая вложенные) необязательными. Рекурсивно обходит все уровни.

Параметры: Один обязательный — тип объекта.

import type { PartialDeep } from 'type-fest';

interface Config {
  server: {
    host: string;
    port: number;
  };
  debug: boolean;
}

type PartialConfig = PartialDeep<Config>;
// {
//   server?: {
//     host?: string;
//     port?: number;
//   };
//   debug?: boolean;
// }

const config: PartialConfig = {
  server: {
    host: 'localhost'
    // port не указан — ошибки нет
  }
};

2. RequiredDeep

Что делает: Делает все свойства объекта (включая вложенные) обязательными. Противоположность PartialDeep.

Параметры: Тип объекта.

import type { RequiredDeep } from 'type-fest';

interface User {
  name?: string;
  address?: {
    city?: string;
  };
}

type StrictUser = RequiredDeep<User>;
// {
//   name: string;
//   address: {
//     city: string;
//   };
// }

// Теперь все поля обязательны
const user: StrictUser = {
  name: 'Bob',
  address: {
    city: 'NYC'
  }
};

3. ReadonlyDeep

Что делает: Делает все свойства объекта (включая вложенные) readonly. Рекурсивно.

Параметры: Тип объекта.

import type { ReadonlyDeep } from 'type-fest';

interface Data {
  value: number;
  nested: {
    items: string[];
  };
}

type ReadonlyData = ReadonlyDeep<Data>;

const data: ReadonlyData = {
  value: 42,
  nested: {
    items: ['a', 'b']
  }
};

// data.value = 100; // Ошибка: нельзя изменить readonly поле
// data.nested.items.push('c'); // Ошибка: метод push недоступен для readonly массива

4. PickDeep

Что делает: Выбирает указанные ключи из объекта, включая вложенные (с использованием точечной нотации).

Параметры: Тип объекта и список ключей (строками через точку для вложенных).

import type { PickDeep } from 'type-fest';

interface User {
  id: number;
  name: string;
  address: {
    city: string;
    zip: number;
  };
}

type UserCity = PickDeep<User, 'id' | 'address.city'>;
// {
//   id: number;
//   address: {
//     city: string;
//   };
// }

const user: UserCity = {
  id: 1,
  address: {
    city: 'London'
  }
};

5. OmitDeep

Что делает: Исключает указанные ключи из объекта, включая вложенные. Противоположность PickDeep.

Параметры: Тип объекта и список ключей для исключения.

import type { OmitDeep } from 'type-fest';

interface Product {
  id: number;
  name: string;
  price: number;
  metadata: {
    createdAt: Date;
    updatedAt: Date;
  };
}

type ProductPublic = OmitDeep<Product, 'metadata.createdAt' | 'metadata.updatedAt'>;
// {
//   id: number;
//   name: string;
//   price: number;
//   metadata: {};
// }

6. Merge

Что делает: Объединяет два типа, при этом свойства второго типа переопределяют свойства первого (поверхностное слияние).

Параметры: Два типа — базовый и переопределяющий.

import type { Merge } from 'type-fest';

interface Base {
  id: number;
  name: string;
}

interface Override {
  name: number; // меняем тип name
  extra: boolean;
}

type Merged = Merge<Base, Override>;
// {
//   id: number;
//   name: number; // тип переопределен
//   extra: boolean;
// }

7. MergeDeep

Что делает: Глубокое слияние двух типов. Рекурсивно объединяет вложенные объекты, а не заменяет их целиком.

Параметры: Два типа.

import type { MergeDeep } from 'type-fest';

interface Base {
  a: {
    b: number;
    c: string;
  };
}

interface Override {
  a: {
    b: string; // меняем тип
    d: boolean; // добавляем новое поле
  };
}

type Merged = MergeDeep<Base, Override>;
// {
//   a: {
//     b: string; // переопределено
//     c: string; // сохранено из Base
//     d: boolean; // добавлено из Override
//   };
// }

8. Simplify

Что делает: «Упрощает» сложные пересекающиеся типы, превращая их в плоскую структуру. Полезно для улучшения читаемости в IDE.

Параметры: Тип.

import type { Simplify } from 'type-fest';

type ComplexType = { a: number } & { b: string } & { c: boolean };
// В IDE будет показано как пересечение

type SimpleType = Simplify<ComplexType>;
// { a: number; b: string; c: boolean; } — плоская структура

9. JsonValue, JsonObject, JsonArray

Что делает: Набор типов для строгой типизации JSON-данных. JsonValue — это объединение всех допустимых JSON-типов, JsonObject — объект с ключами-строками и значениями JsonValue, JsonArray — массив JsonValue.

Параметры: Нет (это конкретные типы).

import type { JsonValue, JsonObject, JsonArray } from 'type-fest';

const data: JsonObject = {
  name: 'Alice',
  age: 30,
  hobbies: ['reading', 'coding'],
  address: {
    city: 'NYC'
  }
};

// Ошибка: undefined не является JsonValue
// const invalid: JsonObject = { key: undefined };

function processJson(input: JsonValue): void {
  if (typeof input === 'string') {
    console.log(input.toUpperCase());
  } else if (Array.isArray(input)) {
    input.forEach(processJson);
  }
}

10. SetNonNullable

Что делает: Исключает null и undefined из типа. Аналог NonNullable из стандартной библиотеки, но работает с объектами.

Параметры: Тип.

import type { SetNonNullable } from 'type-fest';

interface User {
  id: number | null;
  name: string | undefined;
  email: string;
}

type CleanUser = SetNonNullable<User>;
// {
//   id: number;
//   name: string;
//   email: string;
// }

Реальный практический пример использования

Представим, что мы разрабатываем API-клиент для работы с пользователями. Нам нужно:

  • Сделать все поля входных данных необязательными (частичное обновление)
  • Гарантировать, что ответ от сервера содержит все поля
  • Защитить данные от случайных изменений (readonly)
  • Парсить JSON-ответ с правильной типизацией
import type { PartialDeep, RequiredDeep, ReadonlyDeep, JsonObject } from 'type-fest';

// Исходная модель пользователя
interface User {
  id: number;
  name: string;
  email: string;
  settings: {
    theme: 'light' | 'dark';
    notifications: boolean;
  };
}

// Тип для частичного обновления (PATCH-запрос)
type UpdateUserPayload = PartialDeep<User>;

// Тип для ответа от сервера (все поля обязательны)
type UserResponse = RequiredDeep<User>;

// Тип для отображения (только для чтения)
type UserDisplay = ReadonlyDeep<User>;

class UserApiClient {
  async updateUser(id: number, payload: UpdateUserPayload): Promise<UserResponse> {
    const response = await fetch(`/api/users/${id}`, {
      method: 'PATCH',
      body: JSON.stringify(payload)
    });
    const data: JsonObject = await response.json();
    return data as unknown as UserResponse;
  }

  async getUser(id: number): Promise<UserDisplay> {
    const response = await fetch(`/api/users/${id}`);
    const data: JsonObject = await response.json();
    return data as unknown as UserDisplay;
  }
}

// Использование
const client = new UserApiClient();

// Частичное обновление — передаем только изменяемые поля
client.updateUser(1, {
  settings: {
    theme: 'dark'
    // notifications не указано — ошибки нет
  }
}).then(user => {
  console.log(user.name); // ОК
  // user.name = 'New'; // Ошибка: readonly
});

Этот пример демонстрирует, как Type-fest помогает создавать строгие, но гибкие типы для работы с API, избегая дублирования кода.

Советы и типичные

Рекомендаций