Cum să configurezi interfețe globale personalizate (fișiere .d.ts) pentru TypeScript?
În prezent lucrez la un proiect ReactJS care folosește Webpack2 și TypeScript. Totul funcționează perfect, cu excepția unui lucru - nu pot găsi o modalitate de a muta interfețele pe care le-am scris singur în fișiere separate, astfel încât să fie vizibile pentru întreaga aplicație.
Pentru scopuri de prototipare, inițial am avut interfețe definite în fișierele care le folosesc, dar în cele din urmă am început să adaug unele care erau necesare în mai multe clase și atunci au început toate problemele. Indiferent de schimbările pe care le fac în tsconfig.json și indiferent unde pun fișierele, IDE-ul și Webpack-ul se plâng că nu pot găsi numele ("Could not find name 'IMyInterface'").
Iată fișierul meu actual tsconfig.json:
{
"compilerOptions": {
"baseUrl": "src",
"outDir": "build/dist",
"module": "commonjs",
"target": "es5",
"lib": [
"es6",
"dom"
],
"typeRoots": [
"./node_modules/@types",
"./typings"
],
"sourceMap": true,
"allowJs": true,
"jsx": "react",
"moduleResolution": "node",
"rootDir": "src",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": false,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true
},
"exclude": [
"node_modules",
"build",
"scripts",
"acceptance-tests",
"webpack",
"jest",
"src/setupTests.ts"
],
"types": [
"typePatches"
]
}
După cum puteți vedea, tsconfig.json este în rădăcina directorului proiectului, tot codul sursă este în ./src, am plasat fișierele mele personalizate .d.ts în ./typings și le-am inclus în typeRoots.
Am testat-o cu TypeScript 2.1.6 și 2.2.0 și nici unul nu funcționează.
O modalitate de a face să funcționeze totul este să mut directorul meu typings în src și apoi să fac import {IMyInterface} from 'typings/blah', dar nu mi se pare corect deoarece nu este ceva ce trebuie să folosesc. Vreau ca acele interfețe să fie pur și simplu disponibile „magiește” în toată aplicația mea.
Iată un fișier exemplu app.d.ts:
interface IAppStateProps {}
interface IAppDispatchProps {}
interface IAppProps extends IAppStateProps, IAppDispatchProps {}
Trebuie să le export sau poate să le declare? Sper să nu trebuiască să le înfășor într-un namespace?!
Actualizare (octombrie 2020)
Văzând că această întrebare este încă surprinzător de populară, am vrut să explic soluția în mai mult detaliu.
În primul rând, ceea ce ar putea și ar trebui să fie confuz pentru oameni este că exemplul de interfață pe care l-am dat la sfârșitul întrebării mele, de fapt, nu are niciun cuvânt cheie export, deși sunt aproape sigur că le aveam în fișierele mele în momentul în care am pus întrebarea. Cred că nu le-am inclus în întrebare gândindu-mă că nu ar face nicio diferență, fie că erau acolo sau nu. Ei bine, se dovedește că acest lucru nu este adevărat și cuvântul cheie export este exact ceea ce face diferența între a putea „folosi” interfețele direct versus a fi nevoie să le import explicit.
Deci, modul în care funcționează în TypeScript este următorul:
Dacă doriți o interfață/tip pe care să o puteți folosi pur și simplu fără a fi nevoie să o importați în modulul care o consumă, respectiva interfață trebuie să se afle într-un fișier .ts sau, ideal, într-un fișier .d.ts fără ca în același fișier să existe importuri sau exporturi. Acest aspect este extrem de important deoarece, de îndată ce exportați cel puțin un element din același fișier, acesta devine un modul și tot ceea ce se află în acel modul trebuie ulterior importat de către cei care îl consumă.
Pentru a vă da un exemplu, să presupunem că doriți să aveți un tip numit Dictionary pe care să îl puteți folosi fără a-l importa. Modul de a-l declara ar fi următorul:
// types.d.ts
interface Dictionary {}
interface Foo {}
interface Bar {}
Pentru a îl folosi, pur și simplu faci:
// consumer.ts
const dict: Dictionary = {};
Totuși, nu va mai funcționa dacă dintr-un motiv oarecare oricare dintre interfețele/tipurile din acel fișier sunt exportate, de exemplu:
// types.d.ts
interface Dictionary {}
interface Foo {}
export interface Bar {}
De asemenea, nu va funcționa dacă există importuri în acel fișier:
// types.d.ts
import { OtherType } from 'other-library';
interface Dictionary {}
interface Foo extends OtherType {}
interface Bar {}
Dacă acesta este cazul, singura modalitate de a putea folosi tipul Dictionary ar fi să îl exporți și apoi să îl imporți în consumator:
// types.d.ts
export interface Dictionary {}
interface Foo {}
export interface Bar {}
// consumer.ts
import { Dictionary } from './types';
const dict: Dictionary = {};
Există o particularitate suplimentară de care trebuie să ții cont atunci când folosești flagul isolatedModules în TypeScript, care, important, este activat implicit (și nu poate fi dezactivat) atunci când folosești Create React App - fișierele .ts TREBUIE să exporte cel puțin un element, altfel vei primi eroarea "All files must be modules when the '--isolatedModules' flag is provided.". Asta înseamnă că plasarea interfeței Dictionary într-un fișier types.ts fără cuvântul cheie export nu va funcționa. Trebuie să fie fie un export dintr-un fișier .ts, fie o declarație fără export într-un fișier .d.ts:
// types.d.ts
interface Dictionary {} // works
export interface Dictionary {} // works
// types.ts
interface Dictionary {} // doesn't work with --isolatedModules enabled
export interface Dictionary {} // works
N.B.
Așa cum menționează @dtabuenc în răspunsul său, modulele ambientale (fișiere .d.ts) sunt descurajate și corecția mea nu ar trebui considerată ca un sfat. Este doar o încercare de a explica cum funcționează modulele normale și modulele ambientale în TypeScript.
Răspuns #1
Interfețele „disponibile magic” sau tipurile globale sunt puternic descurajate și ar trebui lăsate în mare parte moștenirii. De asemenea, nu ar trebui să utilizați fișiere de declarații ambientale (de exemplu fișiere d.ts) pentru codul pe care îl scrieți. Acestea sunt destinate să înlocuiască codul extern non-TypeScript (practic completând tipurile TypeScript în codul JS astfel încât să le puteți integra mai bine cu JavaScript).
Pentru codul pe care îl scrieți, ar trebui să folosiți fișiere simple .ts pentru a defini interfețele și tipurile.
Deși tipurile globale sunt descurajate, răspunsul la problema ta este că există două tipuri de fișiere .ts în Typescript. Acestea sunt numite scripts și modules.
Orice într-un script va fi global. Deci, dacă definiți interfețele în unui script, acestea vor fi disponibile la nivel global în întreaga aplicație (atâta timp cât scriptul este inclus în compilare fie prin tag-uri ///<reference path="">, fie prin files:[] sau includes:[] sau prin **/*.ts implicit în tsconfig.json).
Celălalt tip de fișier este 'module', iar orice se află într-un module va fi privat modulului. Dacă exporți ceva dintr-un modul, va fi disponibil pentru alte module dacă acele alte module aleg să îl importe.
Ce face ca un fișier .ts să fie un „script” sau un „modul”? Ei bine.... dacă folosești import/export oriunde în fișier, acel fișier devine un „modul”. Dacă nu există instrucțiuni import/export, atunci este un script global.
Presupun că ai folosit din greșeală import sau export în declarațiile tale și ai transformat fișierul într-un modul, ceea ce a făcut ca toate interfețele tale să devină private în cadrul acelui modul. Dacă vrei ca ele să fie globale, atunci trebuie să te asiguri că nu folosești declarații import/export în fișierul tău.
Răspunsul #2
Termenul este „interfață globală”, nu „interfață disponibilă în mod magic”.
Și în unele fișiere .d.ts, extind interfața globală Window, astfel:
export {};
declare global {
interface Window {
// Below just informs IDE and/or TS-compiler (it's set in `.js` file).
myGlobalVariable: any;
}
}
Folosind aceeași tehnică, am putea crea interfețe globale personalizate (în loc să extindem pe cele existente).
Exemplu
import React from 'react';
declare global {
interface MyGlobalInterface {
myComponentField: React.Component;
}
}
Notă că deoarece fișierele
.d.tsnu produc niciun output, putem importa orice modul (atâta timp cât modulul este instalat prinpackage.json, bineînțeles).De asemenea, exemplul de mai sus nu are nevoie de linia
export {};, deoarece are unimport, adică regula era să ai cel puțin unimportsau unexport(la momentul scrierii).În cele din urmă, numele și locația fișierului
.d.tsnu contează, dar prefer să plasez fișierulglobals.d.tslângă fișierul js/ts principal al proiectului, doar pentru a fi sigur că este încărcat primul.
