Как делать11 октября 2026 г.9 мин чтения
ReactШаг 1 из 8

TypeScript с нуля: типы, интерфейсы, дженерики

Коротко

TypeScript — это JavaScript с описанием типов: компилятор tsc проверяет программу до запуска и находит опечатки в именах полей, забытые проверки на undefined и необработанные варианты. Сами типы при запуске стираются — выполняется обычный JavaScript. Для старта достаточно установить пакет typescript, включить strict в tsconfig.json и описать данные через type и interface.

Пошаговый урокУровень: НачальныйTypeScriptПрактика: ~45–90 мин

После материала вы сможете

  • описать данные через type и interface с необязательными и readonly-полями
  • сузить union-тип через typeof, сравнение и поле-дискриминант и проверить полноту switch через never
  • написать дженерик-функцию с ограничением extends и keyof
  • принять JSON как unknown, проверить его предикатами и настроить tsconfig.json со strict

Проверено на

TypeScript 7.0.2TypeScript 6.0.3TypeScript 5.9.3Node.js 24 LTS (24.18.0)

Сверено с документацией: 11 октября 2026 г.

Содержание
  1. 01Зачем типы: ошибка до запуска
  2. 02Базовые типы, вывод типов и аннотации
  3. 03type и interface: описываем данные
  4. 04Union, литеральные типы и сужение
  5. 05Дженерики: одна функция для разных типов
  6. 06unknown против any: типизируем JSON с сервера
  7. 07tsconfig и strict: минимальная настройка
  8. 08Практика и следующий шаг

Зачем типы: ошибка до запуска

Возьмём маленький модуль обработки заказов кофейни на JavaScript. Он считает сумму корзины, скидку по промокоду и подписывает статус заказа. В коде три ошибки — найдите их до того, как прочитаете вывод.

js/orders.js
export function subtotal(order) {
  return order.items.reduce((sum, item) => sum + item.price * item.quanity, 0);
}

export function discount(order) {
  return Math.round(subtotal(order) * order.promo.percent / 100);
}

export function statusLabel(status) {
  if (status === "new") return "Новый";
  if (status === "paid") return "Оплачен";
  if (status === "shipped") return "Отгружен";
}
js/main.js
import { discount, statusLabel, subtotal } from "./orders.js";

const order = {
  id: 1042,
  status: "cancelled",
  items: [{ sku: "CAP-M", name: "Капучино", price: 1600, qty: 2 }],
};

console.log(subtotal(order));
console.log(statusLabel(order.status));
console.log(discount(order));
terminal
$ node js/main.js
NaN
undefined
…\js\orders.js:6
  return Math.round(subtotal(order) * order.promo.percent / 100);
                                                  ^

TypeError: Cannot read properties of undefined (reading 'percent')
    at discount (…/js/orders.js:6:51)
    at …

Node.js v24.18.0
Путь сокращён, строки стека опущены. Первые две ошибки не остановили программу, третья — остановила.

JavaScript промолчал дважды: опечатка quanity дала NaN, а для статуса "cancelled" функция вернула undefined. Только третья ошибка — заказ без промокода — уронила программу, и только в момент выполнения. В реальном проекте такие ошибки находит покупатель.

Документация TypeScript формулирует задачу так: TypeScript — статический анализатор типов для программ на JavaScript, инструмент, который запускается до вашего кода и проверяет, что типы в программе согласованы. Тип — это описание того, какие значения допустимы: число, строка, объект с определёнными полями, один из перечисленных вариантов.

  1. Создайте проект

    Нужен Node.js 24 LTS. Установите компилятор как зависимость разработки: тогда у всех участников проекта будет одна и та же версия.

  2. Добавьте скрипты

    check запускает проверку типов без сборки файлов, start выполняет программу. Node.js 24 умеет запускать .ts-файлы сам, просто вырезая типы.

  3. Опишите правила проверки

    Файл tsconfig.json говорит компилятору, где лежит код и насколько строго его проверять. Подробно разберём его в последнем разделе.

bash
mkdir orders-ts
cd orders-ts
npm init -y
npm install -D typescript
npx tsc -v
npx tsc -v на момент проверки выводит Version 7.0.2.
package.json
{
  "name": "orders-ts",
  "private": true,
  "type": "module",
  "scripts": {
    "check": "tsc --noEmit",
    "start": "node src/main.ts"
  },
  "devDependencies": {
    "typescript": "^7.0.2"
  }
}
Поле "type": "module" включает ES-модули — import и export.
tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "target": "es2024",
    "module": "nodenext",
    "noEmit": true,
    "erasableSyntaxOnly": true,
    "verbatimModuleSyntax": true,
    "rewriteRelativeImportExtensions": true
  },
  "include": ["src"]
}

Теперь переименуем orders.js в src/orders.ts и опишем, что функции принимают на вход. Типы Order и OrderStatus лежат в отдельном файле src/types.ts — его разберём в разделе о type и interface. Код функций не меняем.

src/orders.ts
import type { Order, OrderStatus } from "./types.ts";

export function subtotal(order: Order): number {
  return order.items.reduce((sum, item) => sum + item.price * item.quanity, 0);
}

export function discount(order: Order): number {
  return Math.round(subtotal(order) * order.promo.percent / 100);
}

export function statusLabel(status: OrderStatus): string {
  if (status === "new") return "Новый";
  if (status === "paid") return "Оплачен";
  if (status === "shipped") return "Отгружен";
}
Первая версия: тот же код, что в orders.js, плюс типы параметров и результата.
terminal
$ npm run check

> check
> tsc --noEmit

src/orders.ts(4,68): error TS2339: Property 'quanity' does not exist on type 'OrderItem'.
src/orders.ts(8,39): error TS18048: 'order.promo' is possibly 'undefined'.
src/orders.ts(11,51): error TS2366: Function lacks ending return statement and return type does not include 'undefined'.
  • TS2339 — у OrderItem нет поля quanity: опечатку видно сразу, без NaN в отчёте.
  • TS18048 — promo необязательное поле, его может не быть, и обращаться к percent без проверки нельзя.
  • TS2366 — функция обещает вернуть string, но для одного из статусов не возвращает ничего.

Базовые типы, вывод типов и аннотации

Аннотация типа — запись через двоеточие после имени: price: number. Но писать её везде не нужно. По документации, TypeScript везде, где может, выводит типы сам: тип переменной определяется по начальному значению. Handbook прямо советует новичкам писать меньше аннотаций, чем кажется нужным.

string

Значения
строки
Пример
const name = "Капучино"

number

Значения
целые и дробные числа, NaN
Пример
const price = 1600

boolean

Значения
true и false
Пример
let paid = false

string[] или Array<string>

Значения
массив строк
Пример
const names = ["Капучино", "Чизкейк"]

null, undefined

Значения
«нет значения»
Пример
promo?: Promo — поле может отсутствовать

any

Значения
что угодно, проверка отключена
Пример
избегаем, см. раздел про unknown
src/inference.ts
let total = 0; // вывод типа: number
total = "6540 ₸";

const names = ["Капучино", "Чизкейк"]; // string[]
const count = names.length; // number

// у параметров аннотации нужны, тип результата (number) выведен сам
function lineTotal(price: number, qty: number) {
  return price * qty;
}

// без аннотации параметр получил бы неявный any — strict это запрещает
function fee(order) {
  return order.delivery.fee;
}
Учебный файл с намеренными ошибками: положите его в src, запустите проверку и удалите.
terminal
$ npx tsc --noEmit
src/inference.ts(2,1): error TS2322: Type 'string' is not assignable to type 'number'.
src/inference.ts(13,14): error TS7006: Parameter 'order' implicitly has an 'any' type.

Где аннотации нужны: у параметров функций. Компилятор не может угадать, что придёт в fee(order), и без режима strict молча присвоил бы параметру тип any — то есть отключил бы для него проверку. Флаг noImplicitAny, входящий в strict, превращает такую ситуацию в ошибку TS7006. Тип результата функции обычно выводится из return, поэтому у lineTotal его можно не писать.

type и interface: описываем данные

Все сущности модуля описаны в одном файле. interface задаёт форму объекта, type даёт имя любому типу — в том числе объединению вариантов.

src/types.ts
export type OrderStatus = "new" | "paid" | "shipped" | "cancelled";

export interface OrderItem {
  readonly sku: string;
  name: string;
  price: number; // тенге за штуку
  qty: number;
}

export interface Promo {
  code: string;
  percent: number;
}

export type Delivery =
  | { kind: "pickup" }
  | { kind: "courier"; address: string; fee: number };

export interface Order {
  readonly id: number;
  customer: string;
  status: OrderStatus;
  items: OrderItem[];
  promo?: Promo;
  delivery: Delivery;
}
  • promo?: Promo — необязательное поле. При чтении его тип Promo | undefined, и перед использованием нужна проверка.
  • readonly id и readonly sku — поля, которые после создания объекта нельзя переприсвоить. По Handbook, readonly не меняет поведение во время выполнения: это запрет только на этапе проверки.
  • OrderStatus — объединение строковых литералов: статус может быть только одним из четырёх значений, а не любой строкой.
  • Delivery — объединение двух форм объекта с общим полем kind. Такой тип разберём в следующем разделе.
src/readonly.ts
import type { Order } from "./types.ts";

function renumber(order: Order) {
  order.id = 1; // id объявлен как readonly
  order.items[0].sku = "NEW"; // sku тоже readonly; и элемента [0] может не быть
  order.promo.percent = 50; // promo необязателен
}
Учебный файл с намеренными ошибками.
terminal
$ npx tsc --noEmit
src/readonly.ts(4,9): error TS2540: Cannot assign to 'id' because it is a read-only property.
src/readonly.ts(5,3): error TS2532: Object is possibly 'undefined'.
src/readonly.ts(5,18): error TS2540: Cannot assign to 'sku' because it is a read-only property.
src/readonly.ts(6,3): error TS18048: 'order.promo' is possibly 'undefined'.

Вторая строка дала сразу две ошибки. Кроме readonly сработал флаг noUncheckedIndexedAccess: элемент order.items[0] может не существовать, если корзина пустая, поэтому его тип — OrderItem | undefined. Обратите внимание: readonly защищает только само поле. Документация уточняет, что содержимое объекта в readonly-поле менять по-прежнему можно.

Форма объекта

interface
да
type
да

Объединение вариантов (A | B)

interface
нет
type
да

Расширение

interface
interface B extends A
type
пересечение A & B

Дописать поля повторным объявлением

interface
да (declaration merging)
type
нет — повторное имя считается ошибкой
Различия по Handbook (раздел Everyday Types).

Handbook говорит, что в большинстве случаев выбор между ними — дело вкуса, и предлагает эвристику: использовать interface, пока не понадобятся возможности type. Мы так и сделали: объекты — интерфейсами, объединения — через type.

Union, литеральные типы и сужение

Union-тип (объединение) описывает значение, которое может быть одним из нескольких типов: Promo | undefined, "pickup" | "courier". По документации, с объединением разрешены только операции, допустимые для каждого его варианта. Чтобы работать с конкретным вариантом, тип нужно сузить — проверить в коде, какой вариант перед нами. Handbook называет это narrowing: уточнение типа до более конкретного, чем объявленный.

src/orders.ts
import type { Delivery, Order, OrderStatus } from "./types.ts";

export function subtotal(order: Order): number {
  return order.items.reduce((sum, item) => sum + item.price * item.qty, 0);
}

export function discount(order: Order): number {
  if (order.promo === undefined) {
    return 0;
  }
  // здесь order.promo уже имеет тип Promo, а не Promo | undefined
  return Math.round((subtotal(order) * order.promo.percent) / 100);
}

export function deliveryFee(delivery: Delivery): number {
  switch (delivery.kind) {
    case "pickup":
      return 0;
    case "courier":
      return delivery.fee; // fee есть только у курьерской доставки
  }
}

export function orderTotal(order: Order): number {
  return subtotal(order) - discount(order) + deliveryFee(order.delivery);
}

export function statusLabel(status: OrderStatus): string {
  switch (status) {
    case "new":
      return "Новый";
    case "paid":
      return "Оплачен";
    case "shipped":
      return "Отгружен";
    case "cancelled":
      return "Отменён";
    default: {
      // сюда можно попасть, только если в OrderStatus появится новый статус
      const unhandled: never = status;
      return unhandled;
    }
  }
}
Исправленная версия модуля.
  • discount: после if (order.promo === undefined) return 0 компилятор знает, что дальше order.promo — это Promo. Так работает сужение через сравнение.
  • deliveryFee: у обоих вариантов Delivery есть поле kind с литеральным типом. Документация называет такой тип размеченным объединением (discriminated union): проверка kind в switch сужает весь объект, и в ветке "courier" доступно поле fee.
  • statusLabel: ветка default присваивает статус переменной типа never — «значений не бывает». Если все варианты обработаны, сюда ничего не доходит. Если в OrderStatus добавят новый статус, присваивание станет ошибкой.

Проверим последний пункт: добавим в OrderStatus статус "refunded" и запустим проверку, не трогая statusLabel.

terminal
$ npx tsc --noEmit
src/orders.ts(40,13): error TS2322: Type '"refunded"' is not assignable to type 'never'.

Компилятор показал ровно то место, которое нужно дописать. Без такой проверки функция вернула бы undefined, как в исходном JavaScript. Это приём из Handbook — exhaustiveness checking, проверка полноты.

typeof

Пример
typeof value === "string"
Что узнаёт компилятор
примитивный тип значения

Сравнение

Пример
order.promo === undefined
Что узнаёт компилятор
значение есть или его нет

Поле-дискриминант

Пример
switch (delivery.kind)
Что узнаёт компилятор
конкретный вариант объединения

Оператор in

Пример
"total" in data
Что узнаёт компилятор
у объекта есть такое поле

Array.isArray, instanceof

Пример
error instanceof Error
Что узнаёт компилятор
массив или экземпляр класса

Предикат типа

Пример
function isItem(v: unknown): v is OrderItem
Что узнаёт компилятор
то, что гарантирует ваша функция-проверка

Дженерики: одна функция для разных типов

Иногда функция одинаково работает с данными любой формы: взять первый элемент, найти запись по id, собрать значения одного поля. Если объявить параметр как any[], функция примет что угодно, но, как объясняет Handbook, потеряет информацию о типе: на выходе тоже будет any. Дженерик (generic) решает это через параметр типа — переменную, которая хранит не значение, а тип.

src/utils.ts
// T — параметр типа: «какой массив пришёл, элемент такого же типа и вернётся»
export function first<T>(items: readonly T[]): T | undefined {
  return items[0];
}

// T обязан иметь числовое поле id — это ограничение (constraint)
export function findById<T extends { id: number }>(
  items: readonly T[],
  id: number,
): T | undefined {
  return items.find((item) => item.id === id);
}

// K — только существующие ключи T, а результат — массив значений этого поля
export function pluck<T, K extends keyof T>(items: readonly T[], key: K): T[K][] {
  return items.map((item) => item[key]);
}
  • first<T> — T подставляется при вызове автоматически. Для first(order.items) компилятор выведет T = OrderItem, а результат будет OrderItem | undefined: массив может быть пустым.
  • findById<T extends { id: number }> — ограничение (constraint): подойдёт любой массив объектов, у которых есть числовое id, и вернётся объект того же типа, а не урезанный { id: number }.
  • pluck<T, K extends keyof T> — keyof T означает «имя одного из полей T». Результат T[K][] — массив значений именно этого поля: для "price" получится number[], для "name" — string[].
src/generic-error.ts
import type { Order } from "./types.ts";
import { first, pluck } from "./utils.ts";

function report(order: Order) {
  const prices = pluck(order.items, "price"); // number[]
  const titles = pluck(order.items, "title"); // такого поля нет
  const top = first(order.items);
  console.log(top.name); // first может вернуть undefined
}
Учебный файл с намеренными ошибками.
terminal
$ npx tsc --noEmit
src/generic-error.ts(6,37): error TS2345: Argument of type '"title"' is not assignable to parameter of type 'keyof OrderItem'.
src/generic-error.ts(8,15): error TS18048: 'top' is possibly 'undefined'.

По документации, обычно параметр типа не указывают явно: компилятор выводит его из аргументов. Явная запись вида first<OrderItem>(items) нужна, когда вывести тип не получается. Если дженерик-функцию трудно прочитать, это сигнал упростить её: параметр типа нужен там, где он связывает вход и выход.

unknown против any: типизируем JSON с сервера

TypeScript проверяет только ваш код. Данные, которые приходят по сети, из localStorage или от пользователя, компилятор увидеть не может. JSON.parse возвращает any, и это опасно: по Handbook, any отключает проверку типов, и дальше любая опечатка проходит молча. Безопасная альтернатива — unknown: документация описывает его как тип для любого значения, с которым ничего нельзя делать, пока вы не докажете, что это за значение.

src/unknown.ts
const fromAny: any = JSON.parse(`{ "total": 6540 }`);
fromAny.totl.toFixed(); // компилятор молчит — упадёт при запуске

const fromUnknown: unknown = JSON.parse(`{ "total": 6540 }`);
fromUnknown.total; // ошибка: сначала докажите, что это объект с полем total

if (typeof fromUnknown === "object" && fromUnknown !== null && "total" in fromUnknown) {
  console.log(fromUnknown.total); // тип поля здесь — unknown, его тоже нужно проверить
}
Учебный файл: any пропускает опечатку totl, unknown — нет.
terminal
$ npx tsc --noEmit
src/unknown.ts(5,1): error TS18046: 'fromUnknown' is of type 'unknown'.

Чтобы превратить unknown в Order, нужны проверки во время выполнения. Функции с возвращаемым типом value is OrderItem — предикаты типа: если функция вернула true, компилятор сужает значение до OrderItem.

src/parse.ts
import type { Delivery, Order, OrderItem, OrderStatus, Promo } from "./types.ts";

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null;
}

function isStatus(value: unknown): value is OrderStatus {
  return value === "new" || value === "paid" || value === "shipped" || value === "cancelled";
}

function isItem(value: unknown): value is OrderItem {
  return (
    isRecord(value) &&
    typeof value.sku === "string" &&
    typeof value.name === "string" &&
    typeof value.price === "number" &&
    typeof value.qty === "number"
  );
}

function isPromo(value: unknown): value is Promo {
  return isRecord(value) && typeof value.code === "string" && typeof value.percent === "number";
}

function isDelivery(value: unknown): value is Delivery {
  if (!isRecord(value)) return false;
  if (value.kind === "pickup") return true;
  return (
    value.kind === "courier" &&
    typeof value.address === "string" &&
    typeof value.fee === "number"
  );
}

export function parseOrder(json: string): Order {
  const data: unknown = JSON.parse(json);

  if (
    !isRecord(data) ||
    typeof data.id !== "number" ||
    typeof data.customer !== "string" ||
    !isStatus(data.status) ||
    !Array.isArray(data.items) ||
    !data.items.every(isItem) ||
    !isDelivery(data.delivery)
  ) {
    throw new Error("Сервер вернул заказ в неожиданном формате");
  }

  const order: Order = {
    id: data.id,
    customer: data.customer,
    status: data.status,
    items: data.items,
    delivery: data.delivery,
  };
  if (isPromo(data.promo)) {
    order.promo = data.promo;
  }
  return order;
}
src/main.ts
import { orderTotal, statusLabel } from "./orders.ts";
import { parseOrder } from "./parse.ts";
import { findById, first, pluck } from "./utils.ts";

// так мог бы выглядеть ответ сервера — пока это просто строка
const response = `{
  "id": 1042,
  "customer": "Алия",
  "status": "paid",
  "items": [
    { "sku": "CAP-M", "name": "Капучино", "price": 1600, "qty": 2 },
    { "sku": "CHS-1", "name": "Чизкейк", "price": 2400, "qty": 1 }
  ],
  "promo": { "code": "AUTUMN", "percent": 10 },
  "delivery": { "kind": "courier", "address": "ул. Абая, 10", "fee": 1500 }
}`;

const order = parseOrder(response);

console.log(`Заказ ${order.id}: ${statusLabel(order.status)}`);
console.log(pluck(order.items, "name").join(", "));
console.log(`К оплате: ${orderTotal(order)} ₸`);

const top = first(order.items); // тип: OrderItem | undefined
console.log(top?.name ?? "корзина пуста");

const found = findById([order], 1042); // тип: Order | undefined
console.log(found ? "нашли заказ" : "нет такого заказа");

try {
  parseOrder(`{ "id": "1042" }`);
} catch (error) {
  console.log(error instanceof Error ? error.message : error);
}
terminal
$ npm run check && npm start

> check
> tsc --noEmit


> start
> node src/main.ts

Заказ 1042: Оплачен
Капучино, Чизкейк
К оплате: 6540 ₸
Капучино
нашли заказ
Сервер вернул заказ в неожиданном формате
Проверка типов прошла без ошибок, программа выполнилась. Последняя строка — ответ сервера с id-строкой отклонён.

Инженерный компромисс: ручные проверки, как в parse.ts, хороши для учебного примера и пары эндпоинтов. В проектах с десятками ответов API обычно используют библиотеку описания схем (например, Zod), где тип и проверка выводятся из одного описания и не расходятся.

tsconfig и strict: минимальная настройка

tsconfig.json хранит настройки компилятора, чтобы не передавать их флагами. Наш файл короткий, и каждая строка в нём нужна.

strict: true

Что делает
Включает семейство строгих проверок, среди них noImplicitAny и strictNullChecks. Документация рекомендует strictNullChecks везде, где это практически возможно.

noUncheckedIndexedAccess

Что делает
Добавляет undefined к типу элемента при доступе по индексу: items[0] — это OrderItem | undefined. В strict не входит.

target: es2024

Что делает
Какой версии JavaScript соответствует результат и какие встроенные API считаются доступными.

module: nodenext

Что делает
Правила модулей как в Node.js: с учётом "type": "module" и с расширениями в путях импорта.

noEmit

Что делает
Только проверять, не создавать .js-файлы: код выполняет сам Node.js.

erasableSyntaxOnly

Что делает
Запрещает конструкции TypeScript, которые нельзя просто вырезать (enum, namespace с кодом, parameter properties): Node.js их не выполнит.

verbatimModuleSyntax

Что делает
Требует import type для импорта одних только типов, чтобы при вырезании типов не оставались лишние импорты.

rewriteRelativeImportExtensions

Что делает
Разрешает импорты с расширением .ts — Node.js требует указывать расширение файла.

Что важно знать о версиях. TypeScript 7.0 — новый компилятор, переписанный на Go: по объявлению команды, полная сборка ускоряется примерно в 8–12 раз, а проверка типов совместима с 6.0. Однако программного API у 7.0 пока нет, поэтому инструменты, которые встраивают TypeScript (например, typescript-eslint), работают через совместимый пакет 6.0, а шаблоны вроде Vite пока ставят TypeScript 6.0. Для этого урока разницы нет: синтаксис и сообщения об ошибках одинаковы.

Node.js 24 запускает .ts-файлы напрямую: по документации Node.js, type stripping (вырезание типов) стабилен с версии 24.12 и включён по умолчанию. Но Node.js не проверяет типы и не читает tsconfig.json — поэтому npm run check перед запуском обязателен. В браузерных проектах ту же роль играет сборщик, например Vite, а проверку делает tsc.

Практика и следующий шаг

Итог урока: типы описывают данные, сужение помогает компилятору понять, какой вариант перед ним, дженерики сохраняют типы в переиспользуемых функциях, а unknown и проверки защищают границу с внешним миром. Дальше по траектории React — компоненты и props: те же интерфейсы будут описывать, какие данные принимает каждый кусок интерфейса.

Как проверено6 · пройденосверено 11 октября 2026 г.
  • запущено, работает

    js/orders.js + js/main.js (исходный JS с тремя ошибками)

    node js/main.js на Node.js 24.18.0: вывод NaN, undefined, TypeError — приведён в статье (путь сокращён)

  • запущено, работает

    src/orders.ts — первая версия (before/orders.ts) и вывод npm run check

    tsc 7.0.2 --noEmit в temp-проекте: ровно три ошибки TS2339, TS18048, TS2366 — вывод скопирован

  • запущено, работает

    src/types.ts, src/orders.ts, src/utils.ts, src/parse.ts, src/main.ts, package.json, tsconfig.json

    npm run check (tsc 7.0.2, strict + noUncheckedIndexedAccess) — 0 ошибок; npm start (node src/main.ts, type stripping) — вывод совпадает со статьёй; дополнительно tsc --noEmit на 6.0.3 и 5.9.3 — 0 ошибок

  • запущено, работает

    Учебные файлы с намеренными ошибками: inference.ts, readonly.ts, generic-error.ts, unknown.ts; добавление "refunded" (never)

    каждый файл по очереди помещён в src и проверен tsc 7.0.2; ошибки в статье — дословный вывод

  • запущено, работает

    Решения упражнений (refunded, locker, sumBy/itemsCount)

    применены к копии проекта (ex/), tsc --noEmit — 0 ошибок, node src/main.ts — работает; фрагменты в статье сверены со скомпилированными файлами программно

  • запущено, работает

    EN-версия кода (переведённые строки и комментарии)

    отдельная копия проекта en/ и en/ex/: тот же набор проверок, вывод ошибок совпадает по строкам и столбцам

«Запущено» — код выполнен во временном проекте или контейнере, вывод в статье взят из прогона. «Частично» — проверено с заглушками вместо внешних сервисов. «Не запускалось» — нужны устройство, аккаунт или ключ; шаги сверены с официальной документацией. Файлы проверочного проекта и логи запусков сохранены в архиве InterLAB.

Расскажите о задаче — мы изучим запрос и свяжемся с вами

Есть задача?
Обсудим и оценим — бесплатно.

Расскажите о продукте или проблеме. Предложим архитектуру, срок и стоимость первой версии после изучения задачи.

  • Оценка объёма и рисков до старта
  • NDA — по вашему или нашему шаблону
  • Старт работ в течение недели

Не присылайте через форму пароли, ключи доступа и исходный код — безопасную передачу доступа обсудим отдельно.

Нажимая кнопку, вы соглашаетесь с обработкой персональных данных.