Meistern Sie TypeScript-Deklarationsdateien (.d.ts), um Typsicherheit und Autovervollständigung für jede JavaScript-Bibliothek freizuschalten. Verwenden Sie @types, erstellen Sie eigene Definitionen und handhaben Sie Drittanbietercode wie ein Profi.
Die JavaScript-Umgebung erschließen: Ein tiefer Einblick in TypeScript-Deklarationsdateien
TypeScript hat die moderne Webentwicklung revolutioniert, indem es die statische Typisierung in die dynamische Welt von JavaScript gebracht hat. Diese Typsicherheit bietet unglaubliche Vorteile: Fehler werden bereits zur Kompilierzeit erkannt, eine leistungsstarke Autovervollständigung im Editor wird ermöglicht und große Codebasen werden deutlich wartungsfreundlicher. Eine große Herausforderung entsteht jedoch, wenn wir das riesige Ökosystem bestehender JavaScript-Bibliotheken nutzen wollen – von denen die meisten nicht in TypeScript geschrieben wurden. Wie versteht unser streng typisierter TypeScript-Code die Strukturen, Funktionen und Variablen aus einer untypisierten JavaScript-Bibliothek?
Die Antwort liegt in TypeScript-Deklarationsdateien. Diese Dateien, erkennbar an ihrer .d.ts-Erweiterung, sind die essentielle Brücke zwischen der TypeScript- und der JavaScript-Welt. Sie fungieren als Blaupause oder API-Vertrag und beschreiben die Typen einer Drittanbieterbibliothek, ohne deren tatsächliche Implementierung zu enthalten. In diesem umfassenden Leitfaden werden wir alles untersuchen, was Sie wissen müssen, um Typdefinitionen für jede JavaScript-Bibliothek in Ihren TypeScript-Projekten sicher zu verwalten.
Was genau sind TypeScript-Deklarationsdateien?
Stellen Sie sich vor, Sie haben einen Auftragnehmer eingestellt, der nur eine andere Sprache spricht. Um effektiv mit ihm zusammenzuarbeiten, benötigen Sie einen Übersetzer oder eine detaillierte Reihe von Anweisungen in einer Sprache, die Sie beide verstehen. Eine Deklarationsdatei dient genau diesem Zweck für den TypeScript-Compiler (den Auftragnehmer).
Eine .d.ts-Datei enthält nur Typinformationen. Sie enthält:
- Signaturen für Funktionen und Methoden (Parametertypen, Rückgabetypen).
- Definitionen für Variablen und deren Typen.
- Schnittstellen und Typaliase für komplexe Objekte.
- Klassendefinitionen, einschließlich ihrer Eigenschaften und Methoden.
- Namespace- und Modulstrukturen.
Entscheidend ist, dass diese Dateien keinen ausführbaren Code enthalten. Sie dienen ausschließlich der statischen Analyse. Wenn Sie eine JavaScript-Bibliothek wie Lodash in Ihr TypeScript-Projekt importieren, sucht der Compiler nach einer entsprechenden Deklarationsdatei. Wenn er eine findet, kann er Ihren Code validieren, eine intelligente Autovervollständigung bereitstellen und sicherstellen, dass Sie die Bibliothek korrekt verwenden. Wenn er keine findet, wird ein Fehler wie dieser ausgegeben: Could not find a declaration file for module 'lodash'.
Warum Deklarationsdateien für die professionelle Entwicklung unverzichtbar sind
Die Verwendung von JavaScript-Bibliotheken ohne korrekte Typdefinitionen in einem TypeScript-Projekt untergräbt den eigentlichen Grund für die Verwendung von TypeScript. Betrachten wir ein einfaches Szenario mit der beliebten Utility-Bibliothek Lodash.
Die Welt ohne Typdefinitionen
Ohne eine Deklarationsdatei hat TypeScript keine Ahnung, was lodash ist oder was sie enthält. Um den Code überhaupt zum Kompilieren zu bringen, könnten Sie versucht sein, einen schnellen Fix wie diesen zu verwenden:
const _: any = require('lodash');
const users = [{ 'user': 'barney' }, { 'user': 'fred' }];
// Autocomplete? No help here.
// Type checking? No. Is 'username' the correct property?
// The compiler allows this, but it might fail at runtime.
_.find(users, { username: 'fred' });
In diesem Fall ist die Variable _ vom Typ any. Dies teilt TypeScript im Wesentlichen mit: "Überprüfen Sie nichts, was mit dieser Variablen zusammenhängt." Sie verlieren alle Vorteile: keine Autovervollständigung, keine Typprüfung der Argumente und keine Gewissheit über den Rückgabetyp. Dies ist ein Nährboden für Laufzeitfehler.
Die Welt mit Typdefinitionen
Sehen wir uns nun an, was passiert, wenn wir die erforderliche Deklarationsdatei bereitstellen. Nach der Installation der Typen (die wir als Nächstes behandeln werden) ändert sich die Erfahrung grundlegend:
import _ from 'lodash';
interface User {
user: string;
active?: boolean;
}
const users: User[] = [{ 'user': 'barney' }, { 'user': 'fred' }];
// 1. Editor provides autocompletion for 'find' and other lodash functions.
// 2. Hovering over 'find' shows its full signature and documentation.
// 3. TypeScript sees that `users` is an array of `User` objects.
// 4. TypeScript knows the predicate for `find` on `User[]` should involve `user` or `active`.
// CORRECT: TypeScript is happy.
const fred = _.find(users, { user: 'fred' });
// ERROR: TypeScript catches the mistake!
// Property 'username' does not exist on type 'User'.
const betty = _.find(users, { username: 'betty' });
Der Unterschied ist wie Tag und Nacht. Wir erhalten vollständige Typsicherheit, eine überlegene Entwicklungserfahrung durch Tools und eine drastische Reduzierung potenzieller Fehler. Dies ist der professionelle Standard für die Arbeit mit TypeScript.
Die Hierarchie der Suche nach Typdefinitionen
Wie erhalten Sie also diese magischen .d.ts-Dateien für Ihre Lieblingsbibliotheken? Es gibt einen etablierten Prozess, der die überwiegende Mehrheit der Szenarien abdeckt.
Schritt 1: Überprüfen Sie, ob die Bibliothek ihre eigenen Typen bündelt
Das beste Szenario ist, wenn eine Bibliothek in TypeScript geschrieben ist oder ihre Maintainer offizielle Deklarationsdateien innerhalb desselben Pakets bereitstellen. Dies wird für moderne, gut gewartete Projekte immer üblicher.
So überprüfen Sie es:
- Installieren Sie die Bibliothek wie gewohnt:
npm install axios - Sehen Sie im Ordner der Bibliothek in
node_modules/axiosnach. Sehen Sie dort.d.ts-Dateien? - Überprüfen Sie die Datei
package.jsonder Bibliothek auf ein Feld"types"oder"typings". Dieses Feld verweist direkt auf die Hauptdeklarationsdatei. Beispielsweise enthält die Dateipackage.jsonvon Axios Folgendes:"types": "index.d.ts".
Wenn diese Bedingungen erfüllt sind, sind Sie fertig! TypeScript findet und verwendet diese gebündelten Typen automatisch. Es sind keine weiteren Maßnahmen erforderlich.
Schritt 2: Das DefinitelyTyped-Projekt (@types)
Für die Tausenden von JavaScript-Bibliotheken, die ihre eigenen Typen nicht bündeln, hat die globale TypeScript-Community eine unglaubliche Ressource geschaffen: DefinitelyTyped.
DefinitelyTyped ist ein zentralisiertes, von der Community verwaltetes Repository auf GitHub, das qualitativ hochwertige Deklarationsdateien für eine große Anzahl von JavaScript-Paketen hostet. Diese Definitionen werden in der npm-Registrierung unter dem Scope @types veröffentlicht.
So verwenden Sie es:
Wenn eine Bibliothek wie lodash ihre eigenen Typen nicht bündelt, installieren Sie einfach das entsprechende Paket @types als Entwicklungsabhängigkeit:
npm install --save-dev @types/lodash
Die Namenskonvention ist einfach und vorhersehbar: Für ein Paket namens package-name befinden sich die Typen fast immer unter @types/package-name. Sie können auf der npm-Website oder direkt im DefinitelyTyped-Repository nach verfügbaren Typen suchen.
Warum --save-dev? Deklarationsdateien werden nur während der Entwicklung und Kompilierung benötigt. Sie enthalten keinen Laufzeitcode und sollten daher nicht in Ihrem endgültigen Produktionsbundle enthalten sein. Durch die Installation als devDependency wird diese Trennung sichergestellt.
Schritt 3: Wenn keine Typen vorhanden sind - Schreiben Sie Ihre eigenen
Was ist, wenn Sie eine ältere, Nischen- oder interne private Bibliothek verwenden, die keine Typen bündelt und nicht auf DefinitelyTyped vorhanden ist? In diesem Fall müssen Sie die Ärmel hochkrempeln und Ihre eigene Deklarationsdatei erstellen. Dies mag zwar einschüchternd klingen, aber Sie können einfach anfangen und nach Bedarf weitere Details hinzufügen.
Der Quick Fix: Shorthand Ambient Module Declaration
Manchmal müssen Sie Ihr Projekt nur ohne Fehler kompilieren, während Sie eine geeignete Typisierungsstrategie entwickeln. Sie können eine Datei in Ihrem Projekt erstellen (z. B. declarations.d.ts oder types/global.d.ts) und eine Shorthand-Deklaration hinzufügen:
// in a .d.ts file
declare module 'some-untyped-library';
Dies teilt TypeScript mit: "Vertrauen Sie mir, es existiert ein Modul namens 'some-untyped-library'. Behandeln Sie einfach alles, was daraus importiert wird, als Typ any." Dies unterdrückt den Compilerfehler, aber wie wir bereits besprochen haben, opfert es die gesamte Typsicherheit für diese Bibliothek. Es ist ein temporärer Patch, keine langfristige Lösung.
Erstellen einer einfachen benutzerdefinierten Deklarationsdatei
Ein besserer Ansatz ist es, mit der Definition der Typen für die Teile der Bibliothek zu beginnen, die Sie tatsächlich verwenden. Nehmen wir an, wir haben eine einfache Bibliothek namens `string-utils`, die eine einzige Funktion exportiert.
// In node_modules/string-utils/index.js
module.exports.capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
Wir können eine Datei string-utils.d.ts in einem dedizierten `types`-Verzeichnis im Stammverzeichnis unseres Projekts erstellen.
// In my-project/types/string-utils.d.ts
declare module 'string-utils' {
export function capitalize(str: string): string;
// You could add other function definitions here as you use them
// export function slugify(str: string): string;
}
Nun müssen wir TypeScript mitteilen, wo es unsere benutzerdefinierten Typdefinitionen finden kann. Dies tun wir in tsconfig.json:
{
"compilerOptions": {
// ... other options
"baseUrl": ".",
"paths": {
"*": ["types/*"]
}
}
}
Mit diesem Setup findet TypeScript bei import { capitalize } from 'string-utils' Ihre benutzerdefinierte Deklarationsdatei und bietet die von Ihnen definierte Typsicherheit. Sie können diese Datei schrittweise erweitern, wenn Sie weitere Funktionen der Bibliothek verwenden.
Tiefer eintauchen: Erstellen von Deklarationsdateien
Lassen Sie uns einige fortgeschrittenere Konzepte untersuchen, denen Sie beim Schreiben oder Lesen von Deklarationsdateien begegnen werden.
Deklarieren verschiedener Arten von Exporten
JavaScript-Module können Dinge auf verschiedene Weise exportieren. Ihre Deklarationsdatei muss mit der Exportstruktur der Bibliothek übereinstimmen.
- Benannte Exporte: Dies ist die häufigste. Wir haben sie oben mit `export function capitalize(...)` gesehen. Sie können auch Konstanten, Schnittstellen und Klassen exportieren.
- Standardexport: Für Bibliotheken, die `export default` verwenden.
- UMD-Globale: Für ältere Bibliotheken, die für die Verwendung in Browsern über ein
<script>-Tag entwickelt wurden, hängen sie sich häufig an das globale `window`-Objekt an. Sie können diese globalen Variablen deklarieren. - `export =` und `import = require()`: Diese Syntax ist für ältere CommonJS-Module, die `module.exports = ...` verwenden. Wenn eine Bibliothek beispielsweise `module.exports = myClass;` ausführt.
declare module 'my-lib' {
export const version: string;
export interface Options { retries: number; }
export function doSomething(options: Options): Promise
declare module 'my-default-lib' {
// For a function default export
export default function myCoolFunction(): void;
// For an object default export
// const myLib = { name: 'lib', version: '1.0' };
// export default myLib;
}
// Declares a global variable '$' of a certain type
declare var $: JQueryStatic;
// in my-class.d.ts
declare class MyClass { constructor(name: string); }
export = MyClass;
// in your app.ts
import MyClass = require('my-class');
const instance = new MyClass('test');
Dies ist zwar bei modernen ES-Modulen weniger verbreitet, aber für die Kompatibilität mit vielen älteren, aber immer noch weit verbreiteten Node.js-Paketen von entscheidender Bedeutung.
Modulerweiterung: Erweitern vorhandener Typen
Eine der leistungsstärksten Funktionen ist die Modulerweiterung (auch bekannt als Deklarationsverschmelzung). Auf diese Weise können Sie vorhandenen Schnittstellen, die in der Deklarationsdatei eines anderen Pakets definiert sind, Eigenschaften hinzufügen. Dies ist äußerst nützlich für Bibliotheken mit einer Plugin-Architektur, wie Express oder Fastify.
Stellen Sie sich vor, Sie verwenden eine Middleware in Express, die dem `Request`-Objekt eine `user`-Eigenschaft hinzufügt. Ohne Erweiterung würde TypeScript bemängeln, dass `user` in `Request` nicht existiert.
So können Sie TypeScript über diese neue Eigenschaft informieren:
// in your types/express.d.ts file
// We must import the original type to augment it
import { UserProfile } from './auth'; // Assuming you have a UserProfile type
// Tell TypeScript we're augmenting the 'express-serve-static-core' module
declare module 'express-serve-static-core' {
// Target the 'Request' interface inside that module
interface Request {
// Add our custom property
user?: UserProfile;
}
}
Nun ist das Express-`Request`-Objekt in Ihrer gesamten Anwendung korrekt mit der optionalen `user`-Eigenschaft typisiert, und Sie erhalten vollständige Typsicherheit und Autovervollständigung.
Triple-Slash-Direktiven
Möglicherweise sehen Sie manchmal Kommentare am Anfang von .d.ts-Dateien, die mit drei Schrägstrichen (///) beginnen. Dies sind Triple-Slash-Direktiven, die als Compileranweisungen fungieren.
/// <reference types="..." />: Dies ist die häufigste. Sie schließt explizit die Typdefinitionen eines anderen Pakets als Abhängigkeit ein. Beispielsweise können die Typen für ein WebdriverIO-Plugin Folgendes enthalten:/// <reference types="webdriverio" />, da die eigenen Typen von den WebdriverIO-Core-Typen abhängen./// <reference path="..." />: Dies wird verwendet, um eine Abhängigkeit von einer anderen Datei innerhalb desselben Projekts zu deklarieren. Es ist eine ältere Syntax, die weitgehend durch ES-Modulimporte ersetzt wurde.
Bewährte Verfahren für die Verwaltung von Deklarationsdateien
- Bevorzugen Sie gebündelte Typen: Bevorzugen Sie bei der Auswahl zwischen Bibliotheken diejenigen, die in TypeScript geschrieben sind oder ihre eigenen offiziellen Typdefinitionen bündeln. Dies signalisiert ein Engagement für das TypeScript-Ökosystem.
- Bewahren Sie
@typesindevDependenciesauf: Installieren Sie@types-Pakete immer mit--save-devoder-D. Sie werden nicht für Ihren Produktionscode benötigt. - Richten Sie Versionen aus: Eine häufige Fehlerquelle ist eine Diskrepanz zwischen der Bibliotheksversion und ihrer
@types-Version. Ein Major-Versionssprung in einer Bibliothek (z. B. von v2 zu v3) wird wahrscheinlich API-Änderungen mit sich bringen, die im Paket@typeswidergespiegelt werden müssen. Versuchen Sie, sie synchron zu halten. - Verwenden Sie
tsconfig.jsonzur Steuerung: Die CompileroptionentypeRootsundtypesin Ihrertsconfig.jsonkönnen Ihnen eine detaillierte Kontrolle darüber geben, wo TypeScript nach Deklarationsdateien sucht.typeRootsteilt dem Compiler mit, welche Ordner er überprüfen soll (standardmäßig ist dies./node_modules/@types), undtypesermöglicht es Ihnen, explizit aufzulisten, welche Typpakete eingeschlossen werden sollen. - Geben Sie etwas zurück: Wenn Sie eine umfassende Deklarationsdatei für eine Bibliothek schreiben, die keine hat, sollten Sie in Erwägung ziehen, sie zum DefinitelyTyped-Projekt beizutragen. Dies ist eine fantastische Möglichkeit, der globalen Entwicklergemeinschaft etwas zurückzugeben und Tausenden von anderen zu helfen.
Fazit: Die stillen Helden der Typsicherheit
TypeScript-Deklarationsdateien sind die stillen Helden, die es ermöglichen, die dynamische, weitläufige Welt von JavaScript nahtlos in eine robuste, typsichere Entwicklungsumgebung zu integrieren. Sie sind das kritische Bindeglied, das unsere Tools unterstützt, unzählige Fehler verhindert und unsere Codebasen widerstandsfähiger und selbstdokumentierender macht.
Indem Sie verstehen, wie Sie Ihre eigenen .d.ts-Dateien finden, verwenden und sogar erstellen, beheben Sie nicht nur einen Compilerfehler, sondern verbessern Ihren gesamten Entwicklungsablauf. Sie erschließen das volle Potenzial von TypeScript und dem reichhaltigen Ökosystem von JavaScript-Bibliotheken und schaffen eine leistungsstarke Synergie, die zu besserer, zuverlässigerer Software für ein globales Publikum führt.