🟦 TypeScript

Типы, интерфейсы, generics, утилитарные типы, сужение, условные и маппинг-типы, настройка tsconfig и практика.

Шпаргалки · Программирование · #ts #typescript #web #types

Запуск и инструменты

Задача Команда
Установка npm i -D typescript
Инициализация npx tsc --init
Проверка типов npx tsc --noEmit
Запуск без сборки npx tsx file.ts, Deno, Bun, Node 22+ (--experimental-strip-types)
Линтер typescript-eslint

TypeScript стирается при компиляции: проверки типов работают только на этапе разработки, во время выполнения их нет.

Базовые типы

let n: number = 1;        let s: string = 'a';       let b: boolean = true;
let big: bigint = 10n;    let sym: symbol = Symbol();
let a: number[] = [1];    let arr: Array<string> = [];
let t: [string, number] = ['a', 1];                  // кортеж
let named: [x: number, y: number] = [1, 2];
let u: string | number;                              // объединение
let any1: any;                                        // отключает проверки: избегайте
let unk: unknown;                                     // безопасный «любой»: надо сузить
let nothing: void;     let never1: never;             // never: значения нет (ошибка, бесконечный цикл)
let lit: 'GET' | 'POST' = 'GET';                      // литеральные типы
let nullable: string | null = null;

unknown вместо any: перед использованием его нужно проверить.

type и interface

type ID = string | number;
type Point = { x: number; y: number };
interface User {
  readonly id: number;
  name: string;
  email?: string;                       // необязательное поле
  [extra: string]: unknown;             // индексная сигнатура
}
interface Admin extends User { role: 'admin' }
type WithTs = User & { createdAt: Date };      // пересечение
interface type
Расширение extends, слияние деклараций &
Объединения, примитивы, кортежи нет да
Классы implements да да (для объектных)

Практика: interface для публичных контрактов объектов, type для остального.

Перечисления и константы

enum Role { Admin, User }                        // создаёт код в runtime
const ROLES = ['admin', 'user'] as const;        // кортеж только для чтения
type RoleName = typeof ROLES[number];            // 'admin' | 'user'
const cfg = { mode: 'dev' } as const;            // литеральные типы

Часто достаточно объединения литералов вместо enum.

Функции

function add(a: number, b = 0): number { return a + b; }
const f = (x: number, y?: number): string => String(x + (y ?? 0));
type Handler = (e: Event) => void;
function overload(x: string): string;
function overload(x: number): number;
function overload(x: any) { return x; }
function assertIsString(v: unknown): asserts v is string { if (typeof v !== 'string') throw new Error(); }
function isUser(v: unknown): v is User { return typeof v === 'object' && v !== null && 'id' in v; }

Сужение типов

function show(x: string | number | null) {
  if (x === null) return;
  if (typeof x === 'string') return x.toUpperCase();   // string
  return x.toFixed(2);                                  // number
}
if ('radius' in shape) {}  if (el instanceof HTMLInputElement) {}
type Shape = { kind: 'circle'; r: number } | { kind: 'rect'; w: number; h: number };
function area(s: Shape) { switch (s.kind) { case 'circle': return s.r ** 2; case 'rect': return s.w * s.h; } }

Размеченное объединение (discriminated union) с общим полем kind делает switch исчерпывающим: добавьте default: const _x: never = s;, чтобы компилятор ловил новый случай.

Generics

function first<T>(xs: T[]): T | undefined { return xs[0]; }
function pick<T, K extends keyof T>(o: T, k: K): T[K] { return o[k]; }
interface Box<T = string> { value: T }
class Repo<T extends { id: number }> { items: T[] = []; find(id: number) { return this.items.find(i => i.id === id); } }

Утилитарные типы

Тип Результат
Partial<T> / Required<T> все поля необязательные / обязательные
Readonly<T> поля только для чтения
Pick<T, 'a' | 'b'> / Omit<T, 'a'> выбрать / убрать поля
Record<K, V> словарь
Exclude<U, X> / Extract<U, X> убрать / оставить члены объединения
NonNullable<T> без null и undefined
ReturnType<typeof f> тип результата функции
Parameters<typeof f> кортеж параметров
Awaited<Promise<T>> тип после await
InstanceType<typeof C> тип экземпляра класса

Операторы типов

type Keys = keyof User;                       // 'id' | 'name' | …
type T1 = typeof someValue;                    // тип значения
type Item = Items[number];                      // элемент массива
type Name = User['name'];                       // тип поля
const conf = { a: 1 } satisfies Record<string, number>;   // проверка без расширения типа

Продвинутое

type IsString<T> = T extends string ? true : false;                    // условные типы
type Unwrap<T> = T extends Promise<infer U> ? U : T;                    // infer
type Optional<T> = { [K in keyof T]?: T[K] };                           // маппинг-тип
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] };
type EventName = `on${Capitalize<'click' | 'hover'>}`;                   // шаблонные литералы

Классы

class Account {
  private balance = 0;
  protected id: number;
  readonly owner: string;
  constructor(owner: string, public currency = 'RUB') { this.owner = owner; this.id = 1; }
  deposit(x: number): this { this.balance += x; return this; }
}
abstract class Shape { abstract area(): number; }
class Sq extends Shape implements HasArea { area() { return 1; } }

Модификаторы TS (private) не защищают в runtime; настоящая приватность: #field.

Асинхронность

async function load(id: number): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error(String(res.status));
  return (await res.json()) as User;                 // as — обещание, не проверка!
}

Данные снаружи (JSON, API) не проверяются типами: валидируйте в runtime (zod, valibot) и выводите тип: type User = z.infer<typeof UserSchema>.

Модули и объявления

import type { User } from './types';        // только тип, стирается
export type { User };
declare module 'untyped-lib';                 // заглушка для библиотеки без типов
declare global { interface Window { dataLayer: unknown[] } }

Типы для JS-библиотек: npm i -D @types/node @types/react. Файлы *.d.ts содержат только объявления.

tsconfig.json (основное)

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "dist",
    "paths": { "@/*": ["./src/*"] }
  },
  "include": ["src"]
}

strict включает strictNullChecks, noImplicitAny и другие: оставляйте включённым с первого дня.

Типичные ошибки

  • any распространяется, как вирус: используйте unknown и сужение.
  • Утверждение as T не преобразует данные.
  • Object.keys(o) возвращает string[], а не (keyof T)[].
  • Нарушение вариантности в массивах и колбэках.
  • Циклические импорты типов: выносите общие типы в отдельный файл.

Библиотеки по задачам

Задача Что взять
Валидация и вывод типов zod, valibot, io-ts
Типобезопасный API tRPC, OpenAPI-генераторы (openapi-typescript)
БД Prisma, Drizzle, Kysely
Запуск и сборка tsx, esbuild, Vite, tsup
Тесты типов tsd, expectTypeOf (Vitest)
Утилиты типов type-fest, ts-toolbelt