Comment définissez-vous explicitement une nouvelle propriété sur `window` dans TypeScript?

621

J'ai configuré des espaces de noms globaux pour mes objets en définissant explicitement une propriété window.

window.MyNamespace = window.MyNamespace || {};

TypeScript souligne MyNamespaceet se plaint que:

La propriété 'MyNamespace' n'existe pas sur la valeur de type 'window' any "

Je peux faire fonctionner le code en déclarant MyNamespacecomme variable ambiante et en supprimant l' windowexplicitness, mais je ne veux pas le faire.

declare var MyNamespace: any;

MyNamespace = MyNamespace || {};

Comment puis-je y rester windowet faire plaisir à TypeScript?

En remarque, je trouve particulièrement drôle que TypeScript se plaint car il me dit que windowc'est du type anyqui peut certainement contenir n'importe quoi.

joshuapoehls
la source
voir Lien
MHS

Réponses:

693

Je viens de trouver la réponse à cela dans une autre réponse à une question StackOverflow .

declare global {
    interface Window { MyNamespace: any; }
}

window.MyNamespace = window.MyNamespace || {};

Fondamentalement, vous devez étendre l' windowinterface existante pour lui parler de votre nouvelle propriété.

joshuapoehls
la source
12
Notez le W majuscule dans la fenêtre. Cela m'a fait trébucher.
ajm
2
Je n'ai pas pu compiler cela avec tsc 1.0.1.0. La réponse de Blake Mitchell a fonctionné pour moi, cependant.
Pat
43
Sachez que cela ne fonctionne que lorsqu'il est déclaré dans un .d.tsfichier séparé . Il ne fonctionne pas lorsqu'il est utilisé dans un .tsfichier qui utilise lui-même les importations et les exportations. Voir cette réponse .
cdauth
36
Les declare global { interface Window { ... } }travaux avec tapuscrit 2.5.2, pas besoin de .d.tsfichier comme mentionné ci - dessus
tanguy_k
2
Au début, dactylographié m'a dit: error TS1234: An ambient module declaration is only allowed at the top level in a file.lorsque je l'ai placé en haut du fichier, il a corrigé cette erreur, vous devrez donc peut-être le faire également.
Zelphir Kaltstahl
398

Pour le garder dynamique, utilisez simplement:

(<any>window).MyNamespace
Chinupson
la source
9
Des idées sur la façon de le faire dans un fichier tsx?
velop
2
@martin: Le <any> le rend explicite, donc ça marche très bien, je crois. Autres: Si vous utilisez Typescript avec React ou un autre environnement JSX (résultant en une syntaxe TSX), vous devrez utiliser à la asplace de <>. Voir la réponse de @ david-boyd ci-dessous .
Don
31
Je devais utiliser (fenêtre comme tout) .MonNamespace
Arsalan Ahmad
De même pour this-return (<any> this)['something in scope']
HankCa
1
J'ai dû désactiver tslint sur cette ligne avec/* tslint:disable */
Michael Yagudaev
197

À PARTIR DE TYPESCRIPT ^ 3.4.3 CETTE SOLUTION NE FONCTIONNE PLUS

Ou...

vous pouvez simplement taper:

window['MyNamespace']

et vous n'obtiendrez pas une erreur de compilation et cela fonctionne de la même manière que la frappe window.MyNamespace

Evan Larsen
la source
15
mais vous obtiendrez probablement une erreur tslint ... Si vous en avez un bien sûr
smnbbrv
69
Cela va complètement à l'encontre du typage fort, l'idée derrière TypeScript.
d512
18
@ user1334007 en utilisant des globaux aussi. Cependant, certains codes hérités l'exigent.
nathancahill
3
@Ramesh window['MyNamespace']()(il suffit d'ajouter des crochets)
iBaff
6
"Cela va complètement à l'encontre d'un typage fort, l'idée derrière le TypeScript.". BIEN!
Cody
188

Vous utilisez TSX? Aucune des autres réponses ne fonctionnait pour moi.

Voici ce que j'ai fait:

(window as any).MyNamespace
David Boyd
la source
4
Comme en (<any> window).MyNamespaceréalité
Dmitry Parzhitsky
44
C'est la même chose sauf lors de l'utilisation de TSX, car le <any>obtient est interprété comme JSX, pas un transtypage de type.
Jake Boone
1
Merci @David, cela a fonctionné comme un charme avec la nouvelle application Create React et la version dactylographiée. Auparavant, cela fonctionnait: (<toute> fenêtre).
Jignesh Raval
fenêtre comme tout? Alors pourquoi utiliserais-tu du tapuscrit? Il suffit d'utiliser Javascript x)
MarcoLe
71

La réponse acceptée est ce que j'utilisais auparavant, mais avec TypeScript 0.9. * Cela ne fonctionne plus. La nouvelle définition de l' Windowinterface semble remplacer complètement la définition intégrée, au lieu de l'augmenter.

Je me suis mis à le faire à la place:

interface MyWindow extends Window {
    myFunction(): void;
}

declare var window: MyWindow;

MISE À JOUR: Avec TypeScript 0.9.5, la réponse acceptée fonctionne à nouveau.

Blake Mitchell
la source
2
Cela fonctionne également avec les modules utilisés par TypeScript 2.0.8. Exemple: export default class MyClass{ foo(){ ... } ... } interface MyWindow extends Window{ mc: MyClass } declare var window: MyWindow window.mc = new MyClass() Ensuite, vous pouvez appeler foo (), par exemple à partir de la console Chrome Dev Tools comme mc.foo()
Martin Majewski
C'est une très bonne réponse si vous ne voulez pas déclarer quelque chose de global. De l'autre côté, vous devez appeler declare var...tous les fichiers dont vous avez besoin.
Puce
Plus un pour cette approche car dans ce cas, vous n'entrez pas en conflit avec les autres packages qui étendent la fenêtre globale dans le monorepo.
Dmitrii Sorin
Je vous remercie! Meilleure réponse OMI. Il ne faut pas passer outre Windowpour le simple fait qu'il ne s'agit pas d' une fenêtre vanille. Contourner la vérification de type ou essayer de tromper TS n'est pas non plus la façon de le faire.
Rutger Willems du
Cela ne fonctionne pas à partir de TS 3.6, malheureusement
Jacob Soderlund
65

Global sont "mal" :), je pense que la meilleure façon d'avoir aussi la portabilité est:

Vous exportez d'abord l'interface: (par exemple: ./custom.window.ts)

export interface CustomWindow extends Window {
    customAttribute: any;
}

Deuxièmement, vous importez

import {CustomWindow} from './custom.window.ts';

Fenêtre var globale de troisième distribution avec CustomWindow

declare let window: CustomWindow;

De cette façon, vous n'avez pas de ligne rouge dans différents IDE si vous utilisez avec des attributs existants de l'objet fenêtre, donc à la fin, essayez:

window.customAttribute = 'works';
window.location.href = '/works';

Testé avec Typescript 2.4.x et plus récent!

onalbi
la source
1
vous voudrez peut-être expliquer pourquoi les globaux sont mauvais. Dans notre cas, nous utilisons une API non standardisée sur l'objet fenêtre. Il est polyfilled pour l'instant. Est-ce vraiment mauvais?
Mathijs Segers
1
@MathijsSegers je ne connais pas spécialement votre cas mais .. à propos des globaux sont mauvais n'est pas seulement à propos de javascript .. est dans toutes les langues .. certaines raisons sont: - Vous ne pouvez pas bien appliquer le modèle de conception - Problème de mémoire et de performance (Vous ayez-les partout, essayez d'attacher quelque chose à l'objet String et voyez ce qui se passe lorsque vous créez une nouvelle chaîne) - Collision de nom provoquant un effet secondaire sur le code .. (en particulier sur javascript qui ont une nature asynchrone) Je peux continuer mais je pense que vous pouvez imaginer le reste ...
onalbi
1
@onabi Je parle littéralement d'une fonctionnalité du navigateur. Il est disponible dans le monde entier car il s'agit du navigateur. Vous suggérez vraiment qu'il est toujours faux de choisir des définitions globales? Je veux dire que nous pourrions implémenter Ponyfills, mais cela gonflerait les navigateurs qui prennent en charge la fonctionnalité (par exemple, l'api plein écran). Cela dit, je ne crois pas que tous les globaux soient mauvais.
Mathijs Segers
2
@MathijsSegers à mon avis, la variable globale doit être évitée d'être utilisée ou modifiée là où nous le pouvons .. est vrai que la fenêtre est disponible puisque le navigateur existe mais est également vrai qui était en mutation tout le temps .. donc si par exemple nous définissons maintenant window.feature = 'Fonction'; et cela est utilisé de manière massive sur le code .. que se passe-t-il si window.feature est ajouté par les navigateurs sur tout le code, cette fonctionnalité est prioritaire .. de toute façon j'ai donné une explication de ma phrase est de ne pas aller contre vous .. salutations ...
2018
43

Pour ceux qui utilisent la CLI angulaire, c'est simple:

src / polyfills.ts

declare global {
  interface Window {
    myCustomFn: () => void;
  }
}

my-custom-utils.ts

window.myCustomFn = function () {
  ...
};

Si vous utilisez IntelliJ, vous devez également modifier le paramètre suivant dans l'EDI avant que vos nouveaux polyfills ne soient récupérés:

> File 
> Settings 
> Languages & Frameworks 
> TypeScript 
> check 'Use TypeScript Service'.
Stephen Paul
la source
1
declare globalest l'astuce, et cette réponse n'est pas vraiment spécifique à Angular CLI ...
Avindra Goolcharan
31

Je n'ai pas besoin de le faire très souvent, le seul cas que j'ai eu était lors de l'utilisation de Redux Devtools avec un middleware.

J'ai simplement fait:

const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

Ou vous pourriez faire:

let myWindow = window as any;

et alors myWindow.myProp = 'my value';

Dimitar Nikovski
la source
1
Dans ce cas, vous pouvez également installer le package npm, qui contient toutes les définitions - comme spécifié dans la documentation
bbrinx
Vous pouvez le faire, mais ce n'est pas la réponse à la question, mais plutôt une solution de contournement
Vitalij
Grâce à cela, j'ai pu activer correctement les outils de redux sous tapuscrit en utilisant (fenêtre comme n'importe laquelle) .__ REDUX_DEVTOOLS_EXTENSION__ && (fenêtre comme n'importe laquelle) .__ REDUX_DEVTOOLS_EXTENSION __ ())
danivicario
20

La plupart des autres réponses ne sont pas parfaites.

  • Certains d'entre eux suppriment simplement l'inférence de type pour shop.
  • Certains des autres ne se soucient que de la variable globale en tant qu'espace de nom, mais pas en tant qu'interface / classe

Je rencontre également le même problème ce matin. J'ai essayé tant de "solutions" sur SO, mais aucune d'entre elles ne produit absolument aucune erreur de type et permet de déclencher un saut de type dans IDE (webstorm ou vscode).

Enfin, d'ici

https://github.com/Microsoft/TypeScript/issues/3180#issuecomment-102523512

, Je trouve une solution raisonnable pour attacher des typages pour une variable globale qui agit à la fois comme interface / classe et espace de noms .

L'exemple est ci-dessous:

// typings.d.ts
declare interface Window {
    myNamespace?: MyNamespace & typeof MyNamespace
}

declare interface MyNamespace {
    somemethod?()
}

declare namespace MyNamespace {
    // ...
}

Maintenant, le code ci-dessus fusionne les typages de l'espace de noms MyNamespaceet de l'interface MyNamespacedans la variable globale myNamespace(la propriété de la fenêtre).

e-cloud
la source
2
Merci - c'est le seul que vous pouvez apparemment utiliser dans un contexte ambiant sans module.
Tyler Sebastian
13

Voici comment procéder, si vous utilisez le gestionnaire de définition TypeScript !

npm install typings --global

Créer typings/custom/window.d.ts:

interface Window {
  MyNamespace: any;
}

declare var window: Window;

Installez votre saisie personnalisée:

typings install file:typings/custom/window.d.ts --save --global

Terminé, utilisez-le ! Le tapuscrit ne se plaindra plus

window.MyNamespace = window.MyNamespace || {};
Nik Sumeiko
la source
1
FWIW typingsest devenu obsolète avec Typescript 2.0 (mi-2016) et a été archivé par le propriétaire.
mrm
13

Typscript n'effectue pas de vérification de type sur les propriétés de chaîne.

window["newProperty"] = customObj;

Idéalement, le scénario de variable globale devrait être évité. Je l'utilise parfois pour déboguer un objet dans la console du navigateur.

Irshad
la source
8

Si vous utilisez Typescript 3.x, vous pourrez peut-être omettre la declare globalpartie dans les autres réponses et simplement utiliser à la place:

interface Window {
  someValue: string
  another: boolean
}

Cela a fonctionné avec moi lors de l'utilisation de Typescript 3.3, WebPack et TSLint.

Dana Woodman
la source
Ne fonctionne pas ^3.4.3. Cette réponse a fonctionné pour moi stackoverflow.com/a/42841166/1114926
Vert
7

Pour référence (c'est la bonne réponse):

À l'intérieur d'un .d.tsfichier de définition

type MyGlobalFunctionType = (name: string) => void

Si vous travaillez dans le navigateur, vous ajoutez des membres au contexte de la fenêtre du navigateur en rouvrant l'interface de Window:

interface Window {
  myGlobalFunction: MyGlobalFunctionType
}

Même idée pour NodeJS:

declare module NodeJS {
  interface Global {
    myGlobalFunction: MyGlobalFunctionType
  }
}

Maintenant, vous déclarez la variable racine (qui vivra en fait sur une fenêtre ou globale)

declare const myGlobalFunction: MyGlobalFunctionType;

Ensuite, dans un .tsfichier normal , mais importé comme effet secondaire, vous l'implémentez réellement:

global/* or window */.myGlobalFunction = function (name: string) {
  console.log("Hey !", name);
};

Et enfin, utilisez-le ailleurs dans la base de code, avec:

global/* or window */.myGlobalFunction("Kevin");

myGlobalFunction("Kevin");
Benoit B.
la source
Merci, c'est le meilleur exemple que j'ai trouvé et qui fonctionne bien.
Nick G.
6

Créer une interface personnalisée étend la fenêtre et ajoutez votre propriété personnalisée en option.

Ensuite, laissez le customWindow qui utilise l'interface personnalisée, mais évalué avec la fenêtre d'origine.

Cela a fonctionné avec le [email protected].

interface ICustomWindow extends Window {
  MyNamespace?: any
}

const customWindow:ICustomWindow = window;

customWindow.MyNamespace = customWindow.MyNamespace {} 
shuizhongyuemin
la source
5

Pour ceux qui souhaitent définir une propriété calculée ou dynamique sur l' windowobjet, vous constaterez que cela n'est pas possible avec la declare globalméthode. Pour clarifier ce cas d'utilisation

window[DynamicObject.key] // Element implicitly has an 'any' type because type Window has no index signature

Vous pourriez essayer de faire quelque chose comme ça

declare global {
  interface Window {
    [DyanmicObject.key]: string; // error RIP
  }
}

Ce qui précède fera cependant une erreur. En effet, dans Typescript, les interfaces ne fonctionnent pas bien avec les propriétés calculées et génèrent une erreur comme

A computed property name in an interface must directly refer to a built-in symbol

Pour contourner ce problème, vous pouvez aller avec le suggérer de jeter windowà de <any>sorte que vous pouvez faire

(window as any)[DynamicObject.key]
août
la source
3
C'est le cas en 2019.
Qwerty
4

En utilisant create-react-app v3.3, j'ai trouvé que le moyen le plus simple d'y parvenir était d'étendre le Windowtype dans la génération automatique react-app-env.d.ts:

interface Window {
    MyNamespace: any;
}
Kris Dover
la source
3

Vous devez d'abord déclarer l'objet window dans la portée actuelle.
Parce que tapuscrit voudrait connaître le type de l'objet.
Puisque l'objet fenêtre est défini ailleurs, vous ne pouvez pas le redéfinir.
Mais vous pouvez le déclarer comme suit: -

declare var window: any;

Cela ne redéfinira pas l'objet fenêtre ou ne créera pas une autre variable avec le nom window.
Cela signifie que la fenêtre est définie ailleurs et que vous ne faites que la référencer dans la portée actuelle.

Ensuite, vous pouvez vous référer à votre objet MyNamespace simplement en: -

window.MyNamespace

Ou vous pouvez définir la nouvelle propriété sur l' windowobjet simplement en: -

window.MyNamespace = MyObject

Et maintenant le tapuscrit ne se plaindra pas.

Mav55
la source
puis-je connaître votre cas d'utilisation de vouloir savoir où windowest défini?
Mav55
2

Je voulais l'utiliser dans une bibliothèque Angular (6) aujourd'hui et il m'a fallu un certain temps pour que cela fonctionne comme prévu.

Pour que ma bibliothèque utilise des déclarations, j'ai dû utiliser l' d.tsextension pour le fichier qui déclare les nouvelles propriétés de l'objet global.

Donc à la fin, le fichier s'est retrouvé avec quelque chose comme:

/path-to-angular-workspace/angular-workspace/projects/angular-library/src/globals.d.ts

Une fois créé, n'oubliez pas de l'exposer dans votre public_api.ts.

Ça l'a fait pour moi. J'espère que cela t'aides.

Poignard
la source