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

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

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

Начать курс

ts-toolkit для TypeScript: полное руководство

1. Что это и зачем нужна

ts-toolkit — это мощная библиотека утилит для TypeScript, предназначенная для упрощения повседневных задач разработчика. Она предоставляет набор готовых функций, типов и классов, которые помогают работать с объектами, массивами, строками, функциями и типами данных более эффективно и безопасно. В отличие от стандартной библиотеки JavaScript, ts-toolkit предлагает строго типизированные решения, полностью совместимые с системой типов TypeScript.

Зачем нужна эта библиотека? В современной TypeScript-разработке часто возникает необходимость в выполнении рутинных операций: глубокое копирование объектов, безопасная навигация по вложенным структурам, проверка типов во время выполнения, работа с Option/Result (монады для обработки ошибок) и многое другое. ts-toolkit берёт на себя реализацию этих паттернов, позволяя разработчику сосредоточиться на бизнес-логике. Библиотека особенно полезна в проектах, где требуется высокая надёжность и строгая типизация.

Ключевое преимущество ts-toolkit — это безопасность типов (type safety). Каждая функция спроектирована так, чтобы максимально использовать возможности TypeScript: условные типы, mapped types, infer и другие продвинутые конструкции. Это означает, что многие ошибки, которые могли бы проявиться только в рантайме, будут обнаружены на этапе компиляции.

2. Установка

Установка библиотеки производится через менеджер пакетов npm:

npm install @tool-belt/ts-toolkit

Или через yarn:

yarn add @tool-belt/ts-toolkit

После установки импортируйте необходимые функции в ваш проект:

import { deepClone, safeGet, pipe } from '@tool-belt/ts-toolkit';

3. Быстрый старт

Минимальный рабочий пример, демонстрирующий основные возможности библиотеки:

import { deepClone, isObject, pipe } from '@tool-belt/ts-toolkit';

// Пример 1: Глубокое копирование
const original = { a: 1, b: { c: 2 } };
const cloned = deepClone(original);
cloned.b.c = 42;
console.log(original.b.c); // 2 (оригинал не изменился)

// Пример 2: Проверка типа
console.log(isObject({}));   // true
console.log(isObject(null)); // false

// Пример 3: Композиция функций
const addOne = (x: number) => x + 1;
const double = (x: number) => x * 2;
const addOneThenDouble = pipe(addOne, double);
console.log(addOneThenDouble(5)); // 12

4. Основные методы и функции

4.1. deepClone

Сигнатура: function deepClone<T>(value: T): T

Описание: Выполняет глубокое копирование любого значения, включая вложенные объекты, массивы, даты, Map, Set и другие структуры. Поддерживает циклические ссылки.

import { deepClone } from '@tool-belt/ts-toolkit';

const user = {
  name: 'Alice',
  address: { city: 'NYC', zip: 10001 },
  hobbies: ['reading', 'coding']
};

const clonedUser = deepClone(user);
clonedUser.address.city = 'LA';
console.log(user.address.city); // 'NYC'

4.2. safeGet

Сигнатура: function safeGet<T, K extends keyof T>(obj: T, key: K): T[K] | undefined

Описание: Безопасно получает значение свойства объекта. Возвращает undefined, если свойство отсутствует или объект равен null/undefined, вместо выбрасывания ошибки.

import { safeGet } from '@tool-belt/ts-toolkit';

const data = { user: { name: 'Bob' } };
const name = safeGet(data, 'user')?.name; // 'Bob'
const missing = safeGet(null, 'anything'); // undefined

4.3. pipe

Сигнатура: function pipe<A, B, C>(f1: (a: A) => B, f2: (b: B) => C): (a: A) => C (и перегрузки для большего числа функций)

Описание: Создаёт композицию функций, передавая результат каждой функции как аргумент следующей. Выполняется слева направо.

import { pipe } from '@tool-belt/ts-toolkit';

const trim = (s: string) => s.trim();
const toUpper = (s: string) => s.toUpperCase();
const exclaim = (s: string) => s + '!';

const transform = pipe(trim, toUpper, exclaim);
console.log(transform('  hello ')); // 'HELLO!'

4.4. compose

Сигнатура: function compose<A, B, C>(f2: (b: B) => C, f1: (a: A) => B): (a: A) => C (и перегрузки)

Описание: Аналог pipe, но выполняется справа налево. Полезно для математических композиций.

import { compose } from '@tool-belt/ts-toolkit';

const addTwo = (x: number) => x + 2;
const multiplyByThree = (x: number) => x * 3;

const addThenMultiply = compose(multiplyByThree, addTwo);
console.log(addThenMultiply(5)); // 21 (сначала addTwo, потом multiplyByThree)

4.5. isObject

Сигнатура: function isObject(value: unknown): value is Record<string, unknown>

Описание: Проверяет, является ли значение объектом (но не null, не массивом и не функцией). Возвращает type guard для TypeScript.

import { isObject } from '@tool-belt/ts-toolkit';

console.log(isObject({}));         // true
console.log(isObject([]));         // false
console.log(isObject(null));       // false
console.log(isObject(() => {}));   // false

4.6. isPlainObject

Сигнатура: function isPlainObject(value: unknown): value is Record<string, unknown>

Описание: Проверяет, является ли значение "простым" объектом (созданным через {} или new Object()). Отличается от isObject тем, что исключает экземпляры классов.

import { isPlainObject } from '@tool-belt/ts-toolkit';

class MyClass {}
console.log(isPlainObject({}));        // true
console.log(isPlainObject(new MyClass())); // false

4.7. merge

Сигнатура: function merge<T extends Record<string, unknown>[]>(...objects: T): UnionToIntersection<T[number]>

Описание: Выполняет глубокое слияние нескольких объектов. Последующие объекты перезаписывают предыдущие. Возвращает пересечение типов всех аргументов.

import { merge } from '@tool-belt/ts-toolkit';

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { b: { d: 3 }, e: 4 };
const merged = merge(obj1, obj2);
// Результат: { a: 1, b: { c: 2, d: 3 }, e: 4 }

4.8. pick

Сигнатура: function pick<T extends Record<string, unknown>, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>

Описание: Создаёт новый объект, содержащий только указанные ключи из исходного объекта.

import { pick } from '@tool-belt/ts-toolkit';

const user = { id: 1, name: 'Charlie', email: 'charlie@example.com', password: 'secret' };
const safeUser = pick(user, ['id', 'name', 'email']);
// Результат: { id: 1, name: 'Charlie', email: 'charlie@example.com' }

4.9. omit

Сигнатура: function omit<T extends Record<string, unknown>, K extends keyof T>(obj: T, keys: K[]): Omit<T, K>

Описание: Создаёт новый объект, исключая указанные ключи из исходного объекта. Противоположность pick.

import { omit } from '@tool-belt/ts-toolkit';

const user = { id: 1, name: 'Diana', email: 'diana@example.com', password: 'secret' };
const publicUser = omit(user, ['password']);
// Результат: { id: 1, name: 'Diana', email: 'diana@example.com' }

4.10. debounce

Сигнатура: function debounce<T extends (...args: any[]) => any>(fn: T, delay: number): (...args: Parameters<T>) => void

Описание: Создаёт debounced-версию функции, которая откладывает её выполнение до истечения указанного времени после последнего вызова.

import { debounce } from '@tool-belt/ts-toolkit';

const save = (text: string) => console.log('Saving:', text);
const debouncedSave = debounce(save, 500);

debouncedSave('draft 1');
debouncedSave('draft 2');
// Через 500 мс будет вызвано только: 'Saving: draft 2'

4.11. throttle

Сигнатура: function throttle<T extends (...args: any[]) => any>(fn: T, limit: number): (...args: Parameters<T>) => void

Описание: Создаёт throttled-версию функции, которая гарантирует, что она будет вызываться не чаще одного раза за указанный интервал времени.

import { throttle } from '@tool-belt/ts-toolkit';

const logScroll = () => console.log('Scrolled');
const throttledScroll = throttle(logScroll, 200);

window.addEventListener('scroll', throttledScroll);
// Функция будет вызываться максимум раз в 200 мс

4.12. memoize

Сигнатура: function memoize<T extends (...args: any[]) => any>(fn: T, resolver?: (...args: Parameters<T>) => string): T

Описание: Мемоизирует функцию, кэшируя результаты её вызовов на основе аргументов. Опционально можно указать собственную функцию для генерации ключа кэша.

import { memoize } from '@tool-belt/ts-toolkit';

const expensiveCalc = (n: number) => {
  console.log('Computing...');
  return n * 2;
};

const memoizedCalc = memoize(expensiveCalc);
console.log(memoizedCalc(5)); // 'Computing...' 10
console.log(memoizedCalc(5)); // 10 (без повторного вычисления)

5. Реальный практический пример

Рассмотрим пример создания сервиса для управления пользователями с использованием ts-toolkit. Этот пример демонстрирует комбинирование нескольких функций библиотеки для построения надёжного и типизированного API.

import {
  deepClone,
  safeGet,
  merge,
  pick,
  omit,
  pipe,
  isObject,
  isPlainObject
} from '@tool-belt/ts-toolkit';

// Определяем типы данных
interface Address {
  street: string;
  city: string;
  zipCode: string;
}

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  address: Address;
  createdAt: Date;
  roles: string[];
}

interface UserPublic {
  id: number;
  name: string;
  email: string;
  address: Address;
  roles: string[];
}

// Класс-сервис для управления пользователями
class UserService {
  private users: Map = new Map();

  // Добавление пользователя с глубоким копированием
  addUser(user: User): void {
    // Создаём глубокую копию, чтобы избежать мутаций извне
    const userCopy = deepClone(user);
    this.users.set(user.id, userCopy);
  }

  // Получение пользователя с безопасной навигацией
  getUser(id: number): User | undefined {
    return safeGet(this.users, id);
  }

  // Обновление пользователя с частичным слиянием
  updateUser(id: number, partial: Partial): User | undefined {
    const existing = this.users.get(id);
    if (!existing) return undefined;

    // Глубокое слияние существующих данных с изменениями
    const updated = merge(deepClone(existing), partial);
    this.users.set(id, updated as User);
    return updated as User;
  }

  // Получение публичных данных пользователя (без пароля)
  getUserPublic(id: number): UserPublic | undefined {
    const user = this.users.get(id);
    if (!user) return undefined;

    // Исключаем пароль и дату создания
    return omit(user, ['password', 'createdAt']);
  }

  // Получение только указанных полей
  getUserFields(id: number, fields: (keyof User)[]): Partial | undefined {
    const user = this.users.get(id);
    if (!user) return undefined;

    return pick(user, fields);
  }

  // Валидация пользователя с использованием type guards                    

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