🟦 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 |