programowanieFrontend Developer

Czym są pliki deklaracji w TypeScript, kiedy i dlaczego pisać własne pliki d.ts? Jak strukturyzować użytkownikowskie opisy typów dla zewnętrznych modułów JS?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Historia pytania:

Wiele bibliotek ekosystemu JavaScript oferuje jedynie źródłowe pliki js, nie mając własnych typów. Aby opisać typy zewnętrznych lub niestandardowych bibliotek, a także globalnych zmiennych, w TypeScript wprowadzono specjalny format plików z rozszerzeniem .d.ts (pliki deklaracji). Stały się one standardem zapewniającym informacje o typach i bezpieczeństwo typów w projektach opartych na dowolnych modułach js.

Problem:

Jeśli typy dla zewnętrznych modułów JS nie są zdefiniowane, TypeScript jest zmuszony traktować takie importy jako any, co oznacza, że tracisz wszystkie zalety statycznego typowania: błędy w wywołaniach, nieistniejących polach, niewłaściwych parametrach logicznie przechodzą kompilację i ujawniają się dopiero po uruchomieniu kodu. Również niemożliwe jest wykonywanie autouzupełnienia i nawigacji po typach.

Rozwiązanie:

Za pomocą plików deklaracji można ręcznie opisać typy dla dowolnego kodu JS: funkcje, klasy, obiekty, przestrzenie nazw, a nawet globalne stałe. W ten sposób projekt pozostaje typowo bezpieczny niezależnie od pochodzenia zewnętrznej biblioteki.

Przykład kodu:

// hello.d.ts declare module 'hello' { export function sayHello(name: string): string; } // app.ts import { sayHello } from 'hello'; sayHello('TypeScript'); // Typ jest bezpieczny

Kluczowe cechy:

  • Oddzielają opis sygnatur i struktur typów od implementacji (źródłowego kodu JS);
  • Umożliwiają wdrożenie ścisłego typowania nawet w zewnętrznych/nieaktualnych buildach;
  • W plikach .d.ts zabroniona jest implementacja, tylko sygnatury/opis.

Pytania podchwytliwe.

Czy można deklarować implementacje funkcji bezpośrednio w pliku deklaracji?

Nie, w plikach deklaracji dozwolone jest tylko deklarowanie struktur i sygnatur, a nie ich implementacja. Jakiekolwiek ciała funkcji, konstruktorów spowodują błąd kompilacji.

// Nie można: declare function sum(a: number, b: number) { return a + b; }

Gdzie szukać typów dla popularnych modułów npm, jeśli nie ma ich w oryginalnym pakiecie?

W repozytorium DefinitelyTyped (pakiet npm @types/<lib>): prawie wszystkie popularne pakiety mają aktualne typy w postaci osobnych modułów npm.

Czy można opisywać globalne zmienne (nie poprzez import), używając pliku d.ts?

Tak, poprzez mechanizm ambient declarations, na przykład, declare var VERSION: string;. Jest to wygodne do opisywania window.X, globalnych stałych i zmiennych.

Typowe błędy i antywzorce

  • Zapisywanie ciał funkcji i klas w plikach d.ts;
  • Opisywanie niekompletnych lub przestarzałych sygnatur, powodując konflikt z rzeczywistą strukturą;
  • Podłączanie różnych typów dla jednego modułu/globalnej zmiennej, powodując konflikt typów.

Przykład z życia

** Negatywny przypadek W projekcie używana jest biblioteka JS bez typów. Programiści zapomnieli o pliku d.ts i zwracają się do API przez any. Pojawiają się błędy przy aktualizacji biblioteki: stare wywołania łamią się, ale kompilator tego nie zauważa.

Zalety:

  • Szybki start, brak potrzeby dodatkowego opisu.

Wady:

  • Ukryte błędy, niejawne zachowanie, trudne debugowanie przy dużych ilościach kodu.

** Pozytywny przypadek Stworzono własny plik d.ts dla bieżącej biblioteki, sygnatury są utrzymywane w aktualnym stanie, korzysta się z autouzupełnienia i nawigacji IDE.

Zalety:

  • Pełne bezpieczeństwo typów, błędy natychmiast widoczne przy zmianie API;
  • Przyspiesza rozwój, można wprowadzać nowych programistów bez dogłębnej znajomości kodu JS.

Wady:

  • Oddzielne wsparcie plików d.ts, trzeba dbać o synchronizację przy aktualizacjach bibliotek JS.