Avatar ·

Как явно добавить новое свойство объекту window в TypeScript

Всем привет! Я начал изучать TypeScript и столкнулся с проблемой при работе с глобальным объектом `window`. Мне нужно добавить в `window` кастомное свойство, например, для хранения глобального ID сессии или флага, который будет доступен из любого скрипта на странице. В обычном JavaScript я бы просто написал: ```typescript window.myCustomProperty = 'hello world'; ``` Но в TypeScript это вызывает ошибку компиляции: ``` Property 'myCustomProperty' does not exist on type 'Window & typeof globalThis'. ``` Я понимаю, что TypeScript строго типизирован, и у `window` нет такого свойства по умолчанию. Я пробовал разные способы: использовать `(window as any).myCustomProperty`, но это убивает всю типизацию. Пытался создать интерфейс, но не уверен, как его правильно применить. Подскажите, как правильно объявить новое свойство для `window`, чтобы TypeScript не ругался и при этом сохранялась типизация? Нужен production-ready подход, который будет работать и в строгом режиме TypeScript. Заранее спасибо! <<>>

Правильный способ: расширение интерфейса Window через declaration merging

Лучший и самый типобезопасный способ — использовать механизм declaration merging (слияние объявлений) в TypeScript. Вы расширяете существующий интерфейс Window, добавляя в него свои свойства.

Создайте файл global.d.ts (или любой файл с расширением .d.ts) в корне вашего проекта и объявите в нём:

// global.d.ts
interface Window {
  myCustomProperty: string;
  sessionId: number;
  isAppReady: boolean;
}

После этого TypeScript автоматически "подхватит" это объявление, и вы сможете использовать свойства напрямую:

// main.ts
window.myCustomProperty = 'hello world';
console.log(window.myCustomProperty); // работает без ошибок

window.sessionId = 12345;
window.isAppReady = true;

Почему это работает?

В TypeScript интерфейсы с одинаковыми именами автоматически объединяются (merge). Когда вы объявляете interface Window в файле .d.ts, TypeScript добавляет ваши свойства к уже существующему определению Window из стандартной библиотеки (lib.dom.d.ts).

Важные нюансы:

  • Файл должен быть .d.ts — это файл объявлений, который не генерирует код, а только добавляет типы.
  • Глобальная область видимости — объявление будет доступно во всех файлах проекта без дополнительных импортов.
  • Типизация сохраняется — TypeScript будет проверять типы ваших свойств, автодополнение в IDE будет работать.
  • Необязательные свойства — если свойство может отсутствовать, используйте ?::
interface Window {
  myCustomProperty?: string;
}

Альтернативный подход с модулями

Если вы используете модули (ES modules) и хотите избежать глобального загрязнения, можно объявить свойство через declare global внутри модуля:

// utils/windowExtensions.ts
export {};

declare global {
  interface Window {
    myCustomProperty: string;
  }
}

// Теперь можно использовать window.myCustomProperty
// Но это объявление будет видно только после импорта этого файла

Чего делать НЕ стоит:

  • (window as any).prop — убивает типизацию, вы теряете автодополнение и проверку типов
  • window['prop'] = value — то же самое, что и выше
  • Переопределять Window через type — это не сработает, так как type не поддерживает declaration merging

Этот подход используется в крупных проектах (Angular, React, Vue) и считается стандартом для расширения глобальных объектов в TypeScript.

<<>>

Быстрый вариант через type assertion с защитой

Если вам нужно добавить свойство быстро и вы не хотите создавать отдельный файл с объявлениями, можно использовать более безопасную версию as any — через промежуточный интерфейс:

interface CustomWindow {
  myCustomProperty: string;
}

const customWindow = window as unknown as CustomWindow;
customWindow.myCustomProperty = 'hello';

// Или однострочник:
(window as unknown as { myCustomProperty: string }).myCustomProperty = 'hello';

Когда это применимо:

  • Для быстрого прототипирования
  • Когда свойство используется в одном месте
  • В небольших скриптах без строгой архитектуры

Минусы:

  • Не расширяет глобальный тип — в других файлах свойство не будет известно
  • Приходится каждый раз приводить тип
  • Легко забыть про приведение и получить ошибку

Для production-проектов всё же рекомендую первый подход с .d.ts файлом — он чище и масштабируемее.