TypeScript ключевое слово declare

17 июля 2026
Автор

Антон Ларичев

Что такое declare и зачем он нужен

Ключевое слово declare сообщает компилятору TypeScript: «эта переменная, функция или класс существует в рантайме, но объявлена не здесь — не генерируй для неё никакого JavaScript-кода, просто знай её тип».

Такие объявления называют ambient (внешними). Они нужны в трёх основных ситуациях:

  1. Вы подключаете стороннюю библиотеку через <script> без npm-пакета — её глобальная переменная есть в window, но TypeScript о ней не знает.
  2. Вы пишете типы для JavaScript-кода, который не собираетесь переписывать на TypeScript.
  3. Вы расширяете глобальный объект или уже существующий модуль новыми свойствами.
// Без declare — ошибка TS2304: Cannot find name 'grecaptcha'
console.log(grecaptcha.execute());

// С declare — компилятор доволен, JS не генерируется
declare const grecaptcha: {
  execute(siteKey: string, options: { action: string }): Promise<string>;
};
console.log(grecaptcha.execute('key', { action: 'submit' }));

Базовые формы объявления

declare var, let, const

Объявляют типы глобальных переменных. var, let и const здесь не определяют семантику изменяемости — в ambient-контексте они эквивалентны. Принято использовать const для значений, которые не переназначаются.

declare var __DEV__: boolean;
declare let currentUser: { id: number; name: string };
declare const API_BASE_URL: string;

// В коде используем как обычные переменные
if (__DEV__) {
  console.log('Debug mode');
}

declare function

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

declare function trackEvent(name: string, payload?: Record<string, unknown>): void;
declare function formatCurrency(amount: number, currency: string): string;

trackEvent('page_view', { path: '/home' });

declare class

Описывает класс, реализованный в JavaScript. Можно объявлять конструктор, методы и свойства.

declare class EventEmitter {
  on(event: string, listener: (...args: unknown[]) => void): this;
  off(event: string, listener: (...args: unknown[]) => void): this;
  emit(event: string, ...args: unknown[]): boolean;
}

const emitter = new EventEmitter();
emitter.on('data', (chunk) => console.log(chunk));

declare enum

Описывает перечисление, значения которого определены в другом файле или сгенерированы инструментом сборки.

declare enum Direction {
  Up,
  Down,
  Left,
  Right,
}

function move(dir: Direction) {
  // ...
}

move(Direction.Up);

declare namespace

namespace в ambient-контексте описывает объект с вложенными свойствами. Часто встречается в типах старых библиотек, которые добавляют объект в window.

declare namespace MyLib {
  interface Config {
    apiKey: string;
    timeout?: number;
  }

  function init(config: Config): void;
  function destroy(): void;

  const version: string;
}

MyLib.init({ apiKey: 'abc123' });
console.log(MyLib.version);

Можно вкладывать namespace в namespace:

declare namespace Analytics {
  namespace Events {
    function track(name: string): void;
  }

  namespace User {
    function identify(id: string): void;
  }
}

Analytics.Events.track('click');
Analytics.User.identify('u_42');

Файлы объявлений .d.ts

Ambient-объявления принято выносить в файлы с расширением .d.ts. Такие файлы содержат только типы и не компилируются в JavaScript.

Типичная структура проекта:

src/
  index.ts
  api.ts
types/
  globals.d.ts       ← глобальные переменные
  third-party.d.ts   ← типы для JS-библиотек без @types

Пример types/globals.d.ts:

// Переменные, проброшенные webpack DefinePlugin или Vite define
declare const __APP_VERSION__: string;
declare const __BUILD_DATE__: string;

// Тип, добавленный сторонним SDK через script-тег
declare const Stripe: import('@stripe/stripe-js').Stripe;

Чтобы TypeScript видел файлы в types/, добавьте путь в tsconfig.json:

{
  "compilerOptions": {
    "typeRoots": ["./types", "./node_modules/@types"]
  }
}

Или явно включите файлы:

{
  "include": ["src/**/*", "types/**/*"]
}

declare global — расширение глобального пространства

Если файл является модулем (содержит import или export), ambient-объявления на верхнем уровне относятся только к этому модулю. Чтобы расширить глобальное пространство из модуля, используйте declare global.

import { User } from './types';

declare global {
  interface Window {
    currentUser: User;
    featureFlags: Record<string, boolean>;
  }

  // Глобальная функция, добавленная полифиллом
  function queueMicrotask(callback: () => void): void;
}

// Теперь TypeScript знает о window.currentUser
console.log(window.currentUser.name);

Пример с расширением встроенных интерфейсов. Допустим, вы добавляете метод к Array.prototype (не рекомендуется в продакшне, но полезно знать как работает):

export {}; // делаем файл модулем

declare global {
  interface Array<T> {
    last(): T | undefined;
  }
}

Array.prototype.last = function () {
  return this[this.length - 1];
};

const nums = [1, 2, 3];
console.log(nums.last()); // 3

declare module — расширение и описание модулей

Описание модуля без типов

Если вы устанавливаете npm-пакет, у которого нет файлов типов и нет пакета @types, TypeScript выдаст ошибку TS7016: Could not find a declaration file. Решение — создать ambient module:

// types/third-party.d.ts
declare module 'legacy-chart-lib' {
  interface ChartOptions {
    width: number;
    height: number;
    data: number[];
  }

  export function render(container: HTMLElement, options: ChartOptions): void;
  export function destroy(container: HTMLElement): void;
}

Если не хочется описывать типы подробно, можно объявить модуль как any — это заглушит ошибку, но лишит вас автодополнения:

declare module 'some-untyped-package';

Module augmentation — добавление свойств к существующему модулю

Позволяет добавлять новые экспорты к уже типизированному модулю. Часто используется для расширения типов фреймворков.

Пример: добавляем пользовательские свойства к express.Request:

// types/express.d.ts
import { User } from '../src/models/user';

declare module 'express-serve-static-core' {
  interface Request {
    user?: User;
    requestId: string;
  }
}

Теперь в обработчиках Express TypeScript знает о req.user и req.requestId:

import { Request, Response } from 'express';

export function getProfile(req: Request, res: Response) {
  // req.user типизирован как User | undefined
  if (!req.user) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  res.json({ id: req.user.id, name: req.user.name });
}

Типы для нестандартных импортов

Vite, webpack и другие сборщики позволяют импортировать CSS, SVG, изображения. TypeScript об этом не знает:

// types/assets.d.ts
declare module '*.svg' {
  const content: string;
  export default content;
}

declare module '*.png' {
  const content: string;
  export default content;
}

declare module '*.css' {
  const classes: Record<string, string>;
  export default classes;
}

После этого импорты работают без ошибок:

import logo from './logo.svg';
import styles from './button.css';

console.log(logo);           // string — URL или data URI
console.log(styles.button);  // string — CSS-класс

declare в классах

Внутри классов declare используется для объявления свойств, которые существуют в рантайме, но не инициализируются в TypeScript-коде. Это нужно при работе с декораторами или при наследовании от JavaScript-классов.

function Column(target: object, key: string) {
  // декоратор добавляет метаданные
}

class UserEntity {
  declare id: number;       // TypeScript знает о свойстве, не генерирует код инициализации
  declare createdAt: Date;

  @Column
  name: string = '';
}

Без declare TypeScript сгенерировал бы this.id = undefined в конструкторе, что могло бы сломать логику декоратора.

Другой случай — useDefineForClassFields: false в tsconfig. При этой настройке поля класса не используют Object.defineProperty, и declare нужен чтобы явно указать: «это поле есть, но инициализировать его не нужно».

Разница между declare и interface/type

Лёгкая точка путаницы: interface и type тоже не генерируют JavaScript-код. В чём разница?

  • interface и type описывают форму данных — структуры, которые можно использовать для аннотации переменных.
  • declare описывает конкретные сущности — переменные, функции, классы, которые существуют в рантайме под конкретными именами.
// Это описание формы — можно использовать как тип, но нет переменной с именем Config
interface Config {
  apiKey: string;
}

// Это объявление конкретной переменной — TypeScript знает, что config существует в рантайме
declare const config: Config;

// Теперь можно использовать config.apiKey без ошибки
console.log(config.apiKey);

Практический пример: типизация SDK

Предположим, вы подключаете через <script> аналитический SDK, который добавляет window.analytics.

<script src="https://cdn.example.com/analytics.js"></script>

Создаём types/analytics.d.ts:

declare namespace analytics {
  interface EventProperties {
    [key: string]: string | number | boolean | null;
  }

  interface UserTraits {
    email?: string;
    name?: string;
    plan?: 'free' | 'pro' | 'enterprise';
  }

  function track(event: string, properties?: EventProperties): void;
  function identify(userId: string, traits?: UserTraits): void;
  function page(name?: string, properties?: EventProperties): void;
  function reset(): void;
}

declare global {
  interface Window {
    analytics: typeof analytics;
  }
}

Теперь TypeScript даёт полное автодополнение:

// Прямой вызов через глобальное имя
analytics.track('Signup Completed', {
  method: 'google',
  plan: 'pro',
});

analytics.identify('user_123', {
  email: 'user@example.com',
  plan: 'pro',
});

// Или через window
window.analytics.page('Pricing');

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

Ошибка 1: declare в обычном .ts файле вместо .d.ts

Ambient-объявления можно писать в обычных .ts файлах, но это создаёт путаницу. Принято держать их в .d.ts, чтобы было ясно: здесь только типы.

Ошибка 2: забыть export {} при использовании declare global

Если файл не содержит import/export, он считается скриптом, и declare global не нужен — объявления и так глобальные. Если файл является модулем, без export {} или реального экспорта declare global не сработает.

// Неправильно — файл без import/export, declare global избыточен
declare global {
  const MY_VAR: string; // работает, но это антипаттерн
}

// Правильно для файла-модуля
export {};

declare global {
  const MY_VAR: string;
}

Ошибка 3: дублирование declare и реального объявления

Если переменная уже объявлена через const/let/function, добавлять declare не нужно — TypeScript выдаст ошибку о дублировании.

const API_URL = 'https://api.example.com';
declare const API_URL: string; // TS2300: Duplicate identifier

Итоговая шпаргалка

Конструкция Назначение
declare const/let/var Глобальная переменная из внешнего кода
declare function Глобальная функция
declare class Класс из JavaScript
declare namespace Объект с вложенными свойствами
declare module 'pkg' Типы для пакета без типов
declare module 'pkg' (augmentation) Добавление типов к существующему пакету
declare global Расширение глобального пространства из модуля
declare в классе Свойство без инициализации в TypeScript

Понимание declare особенно важно при интеграции TypeScript в существующие JavaScript-проекты, при работе с CDN-библиотеками и при создании собственных пакетов с публичными типами.

Чтобы научиться уверенно работать с системой типов TypeScript — от базовых аннотаций до сложных ambient-объявлений — приходите на курс TypeScript с нуля.

Стрелочка влевоТип функции и стрелочные функции в TypeScriptМассивы в TypeScriptСтрелочка вправо

Постройте личный план изучения Typescript до уровня Middle — бесплатно!

Typescript — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по Typescript

Как правильно использовать тип UnknownИспользование Union в TypeScriptТипы данных в TypeScriptType Guards в TypeScript — как работает сужение типовSymbols в TypeScriptСтруктурная типизация в TypeScriptNull и Undefined в TypeScriptТип never в TypeScriptСужение типов (Narrowing) в TypeScriptПонимание Literal Types. Ключевые аспекты и примеры использованияОператор infer в Typescript?Типы функций в TypeScript. Руководство для разработчиковEnums в TypeScriptДискриминированные объединения в TypeScriptСлужебный тип Awaited в TypeScriptКак типизировать массивы TypeScriptКак правильно использовать тип any
Открыть базу знаний

Лучшие курсы по теме

изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

CSS Flexbox

Антон Ларичев
Гарантия
Бонусы
иконка звёздочки рейтинга4.9
бесплатно
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий