Avatar ·

Как в TypeScript явно добавить новое свойство в глобальный объект `window`?

Привет всем! Пишу на TypeScript недавно, столкнулся с проблемой. Нужно сохранить какие-то глобальные данные в `window`, чтобы они были доступны из разных модулей. Например, я хочу добавить туда свой `myAppConfig`: ```typescript window.myAppConfig = { apiUrl: 'https://api.example.com', debugMode: true }; ``` Но TypeScript выдаёт ошибку: ``` Property 'myAppConfig' does not exist on type 'Window & typeof globalThis'. ``` Я понимаю, что TypeScript знает стандартную структуру `window` и ругается на несуществующее свойство. Как правильно объявить новое свойство? Читал про расширение интерфейсов, но до конца не разобрался. Подскажите, пожалуйста, самый простой и правильный способ. Заранее спасибо! <<>>

Правильное расширение интерфейса Window

Проблема в том, что TypeScript имеет строгую типизацию для глобального объекта window. Чтобы добавить своё свойство, нужно расширить существующий интерфейс Window с помощью декларации слияния (declaration merging).

Самый простой и рекомендуемый способ — создать файл объявлений (например, global.d.ts или types.d.ts) в корне проекта и добавить туда:

// global.d.ts
interface Window {
  myAppConfig: {
    apiUrl: string;
    debugMode: boolean;
  };
}

После этого TypeScript перестанет ругаться, и вы сможете обращаться к window.myAppConfig с полной поддержкой автодополнения и типов.

Важно: Этот подход работает, потому что TypeScript автоматически объединяет несколько объявлений одного интерфейса в один общий тип.

Более гибкий вариант с использованием модулей

Если вы работаете с модулями (файлы с import/export), лучше использовать declare global:

// types.ts (или любой другой модульный файл)
export {};

declare global {
  interface Window {
    myAppConfig: {
      apiUrl: string;
      debugMode: boolean;
    };
  }
}

Этот способ явно указывает, что вы расширяете глобальный интерфейс из модуля. Строчка export {} обязательна, чтобы файл считался модулем.

Альтернатива: приведение типа (type assertion)

Если нужно быстрое решение без создания отдельного файла (например, для прототипа), можно использовать приведение типа:

(window as any).myAppConfig = {
  apiUrl: 'https://api.example.com',
  debugMode: true
};

Но этот способ не рекомендуется для production, так как вы теряете все преимущества TypeScript (автодополнение, проверку типов).

Лучшая практика

  • Создайте отдельный файл types/global.d.ts или src/types/global.d.ts.
  • Используйте осмысленные имена для свойств, чтобы избежать конфликтов.
  • Если свойство может отсутствовать, сделайте его опциональным (myAppConfig?: ...).
  • Для сложных объектов вынесите тип в отдельный интерфейс:
// types.ts
export interface AppConfig {
  apiUrl: string;
  debugMode: boolean;
}

declare global {
  interface Window {
    myAppConfig?: AppConfig;
  }
}

Такой подход делает код чище, переиспользуемым и легко поддерживаемым.

<<>>

Использование namespace для организации глобальных данных

Если вы не хотите загрязнять интерфейс Window множеством свойств, можно создать глобальный namespace. Это особенно удобно, когда у вас много разных глобальных конфигураций.

// global.d.ts
declare namespace MyApp {
  interface Config {
    apiUrl: string;
    debugMode: boolean;
  }
  
  let config: Config;
}

Затем в коде:

// Инициализация
MyApp.config = {
  apiUrl: 'https://api.example.com',
  debugMode: true
};

// Использование
console.log(MyApp.config.apiUrl);

Плюсы: всё глобальное хранится под одним именем, легко найти и рефакторить. Минусы: нужно быть аккуратным с порядком загрузки файлов (в браузере). На практике это решается размещением объявления в самом начале загрузки скриптов.

Этот подход хорошо сочетается с первым вариантом: можно оставить window нетронутым, а все кастомные глобальные данные хранить в своём namespace.