Spring til indhold

TypeScript

JavaScript med typer tjekket før det kører

Hvad det er

TypeScript er et programmeringssprog fra Microsoft, der føjer typeannotationer til JavaScript, sammen med en compiler, tsc, der tjekker dem. Navnet forkortes til TS, og filerne ender på .ts, eller .tsx når de indeholder JSX-markup. JavaScript-syntaks er gyldig TypeScript, og derfor kaldes sproget et supersæt af JavaScript. Compileren er open source under Apache License 2.0.

Typerne lever kun, så længe tjekket står på. Når compileren har tjekket et program, sletter den hver eneste annotation, og ud kommer almindelig JavaScript uden typeinformation og uden et ekstra runtime-bibliotek. TypeScript ændrer aldrig, hvordan JavaScript-kode opfører sig, når den kører. Den ene kendsgerning forklarer både styrken og grænsen: tjekkeren kan ræsonnere om alt, hvad der står i kildekoden, og om intet af det, der ankommer, mens programmet kører.

Typesystemet er strukturelt. To typer er forenelige, når deres form passer, uanset hvad de hedder, og det passer godt til JavaScripts vane med at sende almindelige objekter rundt; C# og Java sammenligner i stedet de erklærede navne. Meget af typningen udledes automatisk, så annotationerne sidder mest på funktionssignaturer og eksporterede værdier. Systemet har også et par bevidste huller. any slår tjekket fra for en værdi, og en assertion skrevet med as beder compileren tage programmørens ord for en type. Begge forsvinder fra outputtet sammen med alt det andet.

Der er tre almindelige måder at køre det på. tsc tjekker et projekt og udsender JavaScript. Bundlere som esbuild fjerner typerne uden at tjekke dem og overlader tjekket til en separat kørsel af tsc --noEmit. Node.js kører siden v23.6.0 og v22.18.0 .ts-filer direkte ved at erstatte typesyntaksen med mellemrum, igen uden typetjek, og accepterer kun syntaks, der kan fjernes på den måde. TypeScript 7.0, annonceret i juli 2026, er en portering af compileren til Go, som holdet bag oplyser typisk er 8 til 12 gange hurtigere ved fulde builds.

Hvorfor det betyder noget

Spørgsmålet, et team skal besvare, er, om et byggetrin og en tjekker gør sig fortjent til pladsen ved at fange fejl, før koden kører. I en kodebase, som flere mennesker ændrer hver uge, gør de som regel. Omdøber man et felt, viser compileren hvert sted, der brugte det, editoren fuldfører og forklarer kode ud fra typerne, og et moduls signatur bliver en kontrakt, som kalderne ikke i det stille kan bryde. Prisen er et tjek i hver byggepipeline, en konfigurationsfil med mange indstillinger og typedefinitioner, der skal følge med de biblioteker, de beskriver.

Strenghed er den indstilling, der betyder mest. Flaget strict slår en familie af tjek til, blandt andre strictNullChecks og noImplicitAny, og håndbogen beskriver resultatet som en drejeknap snarere end en kontakt. Siden TypeScript 6.0 er flaget slået til som standard, så et nyt projekt starter strengt. Et ældre projekt kan stadig have det sat til false, og slår man det til senere, skal hver berørt fil igennem. To nyttige tjek står uden for familien og skal slås til hver for sig: noUncheckedIndexedAccess og exactOptionalPropertyTypes.

Sletningen af typerne sætter grænsen. Et program, der læser JSON fra et netværkskald, en fil, en kø eller en formular, har kun programmørens ord for, hvilken form dataene har, for typerne er væk, når dataene ankommer. Grænserne til omverdenen har derfor brug for validering under kørslen, helst genereret fra det samme skema som typerne, så tjekket og typen aldrig kan blive uenige. TypeScript gør programmets indre konsistent og overlader kanterne til koden.

Tjekkeren fodrer også andre værktøjer. En linter som typescript-eslint kan bede compileren om typeinformation, så en regel kan markere et promise, som ingen venter på, eller en værdi af typen any, der breder sig gennem et modul. Sådan gør et team sine egne konventioner til fejl i stedet for kommentarer i et review.

Sådan ser det ud i praksis

I de projekter, der vises på dette site, holdes TypeScript ærligt af genereret kode og af værktøjer frem for af konventioner. Tre sider viser hvordan.

På én platform bliver frontendens TypeScript-klient aldrig skrevet i hånden. Den genereres ud fra API’ets egen beskrivelse, som Huma udsender ud fra Go-typerne, der igen følger databaseskemaet og dets funktioner. Klienten fylder 44.076 linjer, og 61 mock-handlere, der genereres ved siden af den, lader frontendens tests køre mod den rigtige kontrakt i stedet for en håndskrevet fixture. Hvert hop har sit eget værn. Et kontrakttjek i CI sammenligner det, frontenden sender, med det, API’et forventer, og får buildet til at fejle, når de afviger, med en schema-probe nedenunder, der undersøger de rigtige former. Det dækker valgfrie body-felter, hvor »mangler« og »null« let forveksles, og enums i query-parametre. Et felt, der ændrer sig på den ene side, får nu et build til at fejle minutter efter ændringen, i stedet for at dukke op som et 422 i browseren, efter at uoverensstemmelsen er udgivet. Siden nævner også prisen: et genereringstrin midt i det hele.

En anden side sætter én standard for seks sprog, heriblandt TypeScript. Hver diagnostik fra ESLint tæller som en fejl, der findes intet advarselsniveau at gemme sig i, og inline-suppressions er forbudt, så et fund skal rettes i stedet for at blive gjort tavst. Pre-commit- og pre-push-hooks kører linterne og testene, og grænser for funktions- og fillængde holder modulerne læsbare.

De lintere, der følger med et sprog, når ikke frem til et projekts egne regler, så femten custom ESLint-regler holder det repositorys husmønstre på plads i TypeScript. De hører til en guard-motor med 274 check-scripts, hvoraf 268 er registreret i commit-hooks, og guard-laget har 146 test for sig selv, fordi en ødelagt guard lader alt passere, uden at nogen opdager det. Én regel handler om test snarere end typer: en commit med præfikset fix: skal have en test med, der fejler uden rettelsen. Review-tiden flyttede fra mekanik til design, og siden siger prisen lige ud: det er langsomt at committe.

Hvor det går galt

De fleste fejl i TypeScript opstår, fordi man stoler på typerne længere ud, end compileren kan se.

Et felt med typen string viser sig at være undefined under kørslen. Dataene kom udefra og fik en type i stedet for et tjek. DOM-biblioteket giver res.json() typen promise af any, så compileren accepterer alt, hvad koden gør med resultatet, og en assertion gør tilliden eksplicit uden at gøre den sikker:

const res = await fetch("/api/user");
const user = (await res.json()) as User; // trusted, never checked
user.email.toLowerCase(); // TypeError if email is missing

Behandl data udefra som unknown, valider dem ved grænsen mod et runtime-skema, og generer typen fra det samme skema, så de to følges ad. En lintregel med typeinformation kan derefter markere ethvert any, der slipper igennem.

Kode med typefejl når alligevel i produktion. tsc skriver stadig JavaScript, når den melder fejl, fordi noEmitOnError som standard er false, og bundlere og Node.js fjerner typer uden overhovedet at tjekke dem. Kør tsc --noEmit som et obligatorisk trin i CI og i pre-commit-hooken, og lad buildet fejle på dets exitkode.

Et opslag via indeks giver undefined, som typen havde udelukket. Et array-element eller en nøgle, der læses gennem en indekssignatur, får en type, som om værdien altid er der, også forbi arrayets sidste element. Slå noUncheckedIndexedAccess til, som føjer undefined til den slags læsninger og tvinger koden til at håndtere det. Kombinér det med exactOptionalPropertyTypes, der skelner mellem en egenskab, der mangler, og en, der er sat til undefined.

En fil, der kompilerer med tsc, fejler, når Node.js kører den. Type stripping fjerner kun syntaks, der er uden betydning under kørslen. Enums og namespaces med værdier får Node.js til at stoppe med ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX, og en type, der importeres uden nøgleordet type, behandles som en værdiimport, der fejler under kørslen. Slå erasableSyntaxOnly og verbatimModuleSyntax til, så compileren melder begge dele, før Node.js gør det, skriv import type for typer, og erstat enums med objektliteraler markeret as const.

Læs mere

Ordliste