Перейти к основному содержимому

Переменные и константы в TypeScript

Разработчику

Дальше: Ветвления · Типы и типизация · Коллекции · Справочник — основы типизации


В runtime переменные в TypeScript — те же let и const, что в JavaScript. Отличие — контракт типа: компилятор знает, что можно присвоить, и подсказывает это в IDE. Статья логично следует за типами и типизацией; дальше — ветвления и функции.

База JS: переменные и область видимости. null / undefined в strict — 10.md §strictNullChecks.


const и let

ОбъявлениеПереназначение ссылкиКогда использовать
constнельзяпо умолчанию
letможносчётчики, переприсваивание
varустарелоне использовать
const appName = "IT Universe";
let counter = 0;
counter += 1;

// appName = "другое"; // ошибка

Разбор:

  • const запрещает только переназначение ссылки (config = другойОбъект); поля объекта по-прежнему можно менять, если они не readonly.

Вывод типов (type inference)

TypeScript выводит тип из правой части, если аннотация не указана:

const total = 100; // number
const title = "Отчёт"; // string
const flags = [true, false]; // boolean[]

let score = 10;
score = 20; // OK
// score = "текст"; // ошибка

Разбор:


Обязательно ли писать аннотацию типа

Нет — в большинстве случаев TypeScript выводит тип сам. Явная запись let name: type нужна, где компилятору не хватает контекста.

Когда довериться выводу, когда указать тип явно

СитуацияРекомендацияПочему
const count = 0довериться выводутип 0 или number очевиден
const items = []явно : Item[]иначе never[]
let id; id = "a"явно : stringбез аннотации — any / ошибка
Локальная переменная в функцииобычно выводменьше шума
Экспортируемая функцияявно параметры и часто возвратконтракт модуля
Публичное поле классаявноAPI класса
JSON / fetchunknown + parseвывод даст any в legacy
СитуацияАннотация при объявленииПри присвоенииПри использовании
const x = 10Не нужна (вывод number)Тип уже зафиксированПроверка по выведенному типу
let score = 10Не нужнаНовое значение должно быть numberТо же
let id; id = "a"Нужна (let id: string)Проверка совместимости с объявленным типом
Параметр функцииОбычно да (или контекст callback)Аргументы проверяются при вызове
Возврат функцииЧасто выводится; в API — явноreturn проверяется
Поле объекта в литералеЧасто выводится из значенияДоступ к полю по типу объекта

Аннотация при присвоении (x = value as string) — это утверждение (as), отдельный механизм от объявления типа. Предпочитайте сужение через if — ниже и в Операторы и условные ветвления в TypeScript.

Вся эта проверка выполняется на этапе TypeScript (tsc, IDE), не в runtime — Архитектура компиляции TypeScript и runtime.


Когда нужна явная аннотация

// Инициализация позже — тип обязателен
let userId: string;
userId = "u-42";

// Пустой массив без контекста → never[]
const items: string[] = [];

// Параметр функции (всегда типизируют или выводят из контекста)
function setPort(port: number): void {
console.log(port);
}

Разбор:

  • let x; без типа и без присваивания в strict даёт any (или ошибку при noImplicitAny) — избегайте.
  • Явная аннотация полезна на границах API: публичные функции, экспортируемые константы.

Аннотация и утверждение

КонструкцияПримерСмысл
Аннотацияconst x: string = "a"проверка совместимости
Утверждениеvalue as stringДоверие разработчика, проверка слабее
Утверждение !el!.focus()Разработчик обещает, что здесь не null — осторожно

Предпочитайте аннотации и сужение типа (narrowing) через if и typeof вместо asОператоры и условные ветвления в TypeScript.


readonly на полях и переменных

type Point = {
readonly x: number;
readonly y: number;
};

const p: Point = { x: 10, y: 20 };
// p.x = 5; // ошибка

const ids: readonly number[] = [1, 2, 3];
// ids.push(4); // ошибка

Разбор:

  • readonlyповерхностная защита: вложенный объект внутри поля можно менять, если не помечен.
  • Для глубокой неизменяемости — Readonly<T> рекурсивно или иммутабельные обновления.

Неизменяемость и защита данных в функциях

Неизменяемость (immutability) в TypeScript — договорённость на уровне типов: функция не меняет переданные данные без ведома вызывающего. Runtime JavaScript мутацию не блокирует — readonly исчезает при компиляции, зато компилятор предупредит о нарушении контракта.

type Order = {
readonly id: string;
readonly items: readonly { sku: string; qty: number }[];
};

// Плохо: мутирует аргумент
function addItemBad(order: Order, sku: string): void {
(order.items as { sku: string; qty: number }[]).push({ sku, qty: 1 });
}

// Хорошо: возвращает новый объект
function addItem(order: Order, sku: string): Order {
return {
...order,
items: [...order.items, { sku, qty: 1 }],
};
}
ПриёмЧто даёт
readonly на поляхнельзя переприсвоить поле и вызвать мутирующие методы массива
Readonly<T> / ReadonlyArray<T>обёртка для всего объекта или массива
Возврат нового объекта (spread)вызывающий сохраняет старую ссылку без сюрпризов
Readonly<User> в параметресигнал в API: "вход только для чтения"

const у переменной запрещает переназначение ссылки (order = другойОбъект), но вложенный объект остаётся изменяемым. Для контракта функции типизируйте параметр как Readonly<Order> или используйте отдельный тип OrderUpdate с Partial.

Обновление состояния, объявленного через const, — это новый объект:

type Settings = {
readonly theme: "light" | "dark";
readonly locale: string;
};

function withTheme(s: Settings, theme: Settings["theme"]): Settings {
return { ...s, theme };
}

Деструктуризация

Объекты

type User = {
id: string;
name: string;
email?: string;
};

function printUser({ id, name, email }: User): void {
console.log(id, name, email ?? "—");
}

const u: User = { id: "1", name: "Анна" };
const { name: displayName } = u;

Разбор:

  • Тип указывают у всего параметра { ... }: User, не у каждого поля отдельно.
  • Переименование: { name: displayName }.

Массивы и rest

const pair: [string, number] = ["score", 100];
const [label, value] = pair;

const [head, ...tail] = [1, 2, 3, 4];
// head: number, tail: number[]

Значения по умолчанию

function connect({
host = "localhost",
port = 3000,
}: {
host?: string;
port?: number;
}): void {
console.log(`${host}:${port}`);
}

Деструктуризация в const

const response = {
status: 200 as const,
data: { id: "1", name: "Ann" },
};

const {
data: { id, name },
} = response;

Разбор:

  • Вложенная деструктуризация сохраняет вывод типов для id и name.
  • При сложной форме лучше промежуточная переменная — читаемость.

strictNullChecks и переменные

С strict тип string не принимает null / undefined:

let title: string | null = null;

function render(t: string | null): string {
if (t === null) return "Без названия";
return t.toUpperCase();
}

Optional chaining при доступе к полям:

type Profile = { address?: { city?: string } };

function cityOf(p: Profile): string | undefined {
return p.address?.city;
}

Подробнее — Типы данных и типизация в TypeScript; практика проекта — Рекомендации по разработке на TypeScript.


Переменные и модули

На верхнем уровне модуля const и let не попадают в глобальный объект (в ES-модулях), в отличие от старого var в скриптах.

// utils.ts
export const API_URL = "https://api.example.com";

// main.ts

import { API_URL } from "./utils.js";

Разбор:

  • Экспортируемые константы — частый способ задать контракт конфигурации с типом as const или явным union.

Частые ошибки

ОшибкаПричинаЧто делать
let x; без типанеявный anyинициализировать или : T
Менять поля const-объекта и ждать immutabilityconst ≠ deep freezereadonly / новый объект
const arr = []never[]const arr: Item[] = []
Деструктуризация без типа параметраслабые подсказки: User на параметре
! вместо проверкиложная уверенностьif (x !== null)
var в циклахутечка в замыканияхlet

Практика

  1. Опишите Settings с readonly и функцию updateSettings(s, patch) через spread.
  2. Напишите createOrder({ productId, qty }: { productId: string; qty: number }) с деструктуризацией.
  3. Объявите let token: string | null = null и функцию requireToken() с сужением.
  4. Создайте const routes = ["home", "profile"] as const и тип Route через typeof.
  5. Включите noImplicitAny и исправьте все let без выведенного типа.

Смежные статьи