programowanieFull Stack разработчик

Opisz, jak działa typizacja Date oraz błędy czasu w TypeScript. W jaki sposób opisywać poprawne typy pracy z datą i czasem, oraz jakie problemy mogą wystąpić przy integracji z zewnętrznymi bibliotekami lub natywnym Date?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Historia zagadnienia:

W JavaScript obiekt Date używany jest do reprezentacji dat i czasu i znany jest ze swojej specyfiki działania — mutowalności, złożoności parsowania oraz szczególnych cech stref czasowych. TypeScript używa dla Date standardowych typów JS, jednak typizacja i praca z datą w ścisłym kodzie wymaga szczególnego podejścia.

Problem:

Date w JavaScript to mutowalny obiekt, który może generować nietypowe błędy (na przykład, setMonth() mutuje sam obiekt, co może prowadzić do niespodziewanego zachowania kodu). Ponadto integracja z zewnętrznymi bibliotekami (moment.js, date-fns, Day.js itp.) wymaga zachowania precyzji typów — na przykład moment.Instant lub specyficzne opakowania czasu, które nie są ze sobą kompatybilne. Czasami zwracany typ może być string lub number (timestamp), co prowadzi do błędów przy adnotowaniu typów lub używaniu API.

Rozwiązanie:

TypeScript typizuje nowy egzemplarz Date jako Date, a wszystkie jego metody są dostępne. Przy integracji z innymi bibliotekami ważne jest staranne monitorowanie typów wartości wejściowych i wyjściowych — używać jawnego rzutowania na Date (na przykład, new Date(value)), lub tworzyć niestandardowe typy-kontenery dla daty i czasu. Do konwersji między timestamp (number), ciągiem i obiektem Date należy ściśle określać typy i opisywać funkcje typami lub interfejsami.

Przykład kodu:

function toIsoString(d: Date | number | string): string { if (d instanceof Date) return d.toISOString(); if (typeof d === 'number' || typeof d === 'string') return new Date(d).toISOString(); throw new Error('Nieprawidłowa data'); }

Kluczowe cechy:

  • Date — to mutowalny obiekt, nie type value, a type reference.
  • Przyjmowanie i zwracanie daty z API lub zewnętrznych bibliotek wymaga ścisłego zachowania zgodności typów.
  • Zewnętrzne opakowania typu moment, dayjs wymagają jawnych typów niestandardowych i/lub funkcji type guard.

Pytania z podstępem.

Czy typ "Date" w TypeScript jest prostym type value?

Nie, Date to obiekt, tzn. type reference. Porównywanie dat przez == lub === nie porównuje wartości, tylko referencje.

const d1 = new Date('2022-01-01'); const d2 = new Date('2022-01-01'); d1 === d2; // false

Czy można bezpośrednio przypisywać string lub liczbę do typu Date bez nowej inicjalizacji?

Nie, typ Date nie jest kompatybilny z number ani string. Należy tworzyć nowy egzemplarz: new Date(value).

const d: Date = new Date('2020-01-01'); // const d2: Date = '2020-01-01'; // Błąd typizacji

Czy funkcja przyjmująca Date będzie działać z obiektami zewnętrznych bibliotek (moment, dayjs)?

Nie, chyba że w bibliotece jawnie zaimplementowano rzutowanie/kompatybilność z Date przez valueOf/toDate, to są różne typy, a TypeScript nie rozpozna ich jako Date.

import dayjs from "dayjs"; function doSomething(d: Date) { /* ... */ } doSomething(dayjs()); // Błąd, ponieważ typ Dayjs nie jest kompatybilny z Date

Typowe błędy i antywzorce

  • Używanie dowolnych typów (any) dla daty podczas integracji z API.
  • Mutowanie obiektu Date (set*, setUTC*) podczas przekazywania go do kilku funkcji — ryzyko race condition.
  • Niezgodność typów: próba pracy z zewnętrznym typem jako Date.

Przykład z życia

Negatywny przypadek

W kodzie przyjmowano argument jako any, a następnie zakładano, że to Date. W praktyce API zwracał timestamp w stringu, co powodowało błąd przy wywołaniu .getFullYear().

Zalety:

  • Praca "na wszystko" bez błędów kompilacyjnych.

Wady:

  • Ryzyko błędów runtime.
  • Trudności z łapaniem błędów na produkcji.

Pozytywny przypadek

Programista stworzył jawny typ dla parametru (Date | string | number), wprowadził type guard i funkcja wywoływała new Date jawnie. Kompilator zmusza do jawnego przetwarzania wszystkich typów.

Zalety:

  • Przejrzyste błędy już na etapie kompilacji.
  • Praca jest bardziej niezawodna z różnymi typami wejściowymi daty.

Wady:

  • Trochę więcej kodu szablonowego (type guards).
  • Wymaga zgłębienia struktury typów zewnętrznych bibliotek.