Najlepsze praktyki programowania w TypeScript
Ten artykuł wyjaśnia najlepsze praktyki programowania w TypeScript.
Ten przewodnik omawia praktyczne najlepsze praktyki wykorzystania typów TypeScript w celu zmniejszenia liczby błędów i pisania bardziej czytelnego kodu.
YouTube Video
Najlepsze praktyki programowania w TypeScript
Największą zaletą TypeScript jest 'zapobieganie błędom dzięki typom i jasne określenie zamiaru kodu'.
Najlepsze praktyki to nie tylko zasady, ale zestaw wytycznych służących pisaniu bezpiecznego, czytelnego i łatwego w utrzymaniu kodu. Poniżej przedstawiamy najczęściej stosowane najlepsze praktyki TypeScript wraz z praktycznymi przykładami.
Unikaj any i zawsze nadawaj typom sensowne definicje
Najpierw przyjrzyjmy się kwestii 'unikania any i nadawania znaczących typów'.
any całkowicie wyłącza sprawdzanie typów, przez co traci się sens używania TypeScript. Zamiast używać any, aby coś po prostu działało, ważne jest, aby podawać jak najbardziej precyzyjne typy.
1// Bad
2function parse(data: any) {
3 return data.value;
4}Ten kod może przyjąć dowolną wartość, więc nie można zapobiec błędom w czasie wykonania.
1// Good
2type ParsedData = {
3 value: string;
4};
5
6function parse(data: ParsedData): string {
7 return data.value;
8}Definiując typy, precyzujesz zamiar wejść i wyjść oraz zwiększasz bezpieczeństwo.
Zawsze jawnie definiuj struktury obiektów za pomocą type lub interface.
Następnie przyjrzyjmy się kwestii 'zawsze wyraźnego definiowania struktur obiektów przy użyciu type lub interface'.
Jeśli używasz obiektów ad-hoc, ich struktura może stać się niejasna. Zawsze wydzielaj typy dla wielokrotnego użycia i łatwiejszego utrzymania.
1// Bad
2function createUser(user: { name: string; age: number }) {
3 console.log(user.name);
4}Nawet w małych fragmentach kodu ważne jest wyrobienie sobie nawyku rozdzielania typów.
1// Good
2type User = {
3 name: string;
4 age: number;
5};
6
7function createUser(user: User): void {
8 console.log(user.name);
9}Nazwanie typów znacznie ułatwia zrozumienie całej bazy kodu.
Używaj typów sum (union), aby precyzyjnie reprezentować wszystkie możliwe stany.
Jeśli używasz surowych typów string lub number w warunkach, mogą przepuścić nieoczekiwane wartości. Używając typów złączonych (union types), możesz reprezentować tylko dozwolone stany na poziomie typów.
1// Bad
2function setStatus(status: string) {
3 console.log(status);
4}W tym kodzie bezsensowne ciągi znaków lub nieprawidłowe wartości nie mogą zostać wykryte w czasie kompilacji.
1// Good
2type Status = "idle" | "loading" | "success" | "error";
3
4function setStatus(status: Status): void {
5 console.log(status);
6}Korzystając z typów złączonych, możesz wiarygodnie wyeliminować 'niemożliwe stany' podczas kompilacji. W rezultacie poprawi się bezpieczeństwo rozgałęzień warunkowych oraz niezawodność kodu.
Obsługuj null i undefined jawnie.
Następnie przyjrzyjmy się kwestii 'jawnego obsługiwania wartości null i undefined'.
W TypeScript ważne jest, aby wyrażać możliwy brak wartości w typie. Jeśli pozostawisz niejednoznaczność, może to prowadzić do błędów w czasie wykonania.
1type User = {
2 name: string;
3 email?: string;
4};email może nie istnieć, więc należy to odpowiednio obsłużyć.
1function printEmail(user: User): void {
2 if (user.email) {
3 console.log(user.email);
4 }
5}Zawsze sprawdzaj wartości opcjonalne przed ich użyciem.
Używaj rzutowania typów (as) tylko w ostateczności.
Następnie przyjrzyjmy się kwestii 'nie nadużywania rzutowania typów (type assertions)'.
Asercje typów tymczasowo omijają sprawdzanie typów w TypeScript, aby zadeklarować: 'Wiem, że ta wartość ma ten typ.'. Nadmierne użycie osłabia bezpieczeństwo typów.
1// Bad
2const value = input as string;W tym kodzie, nawet jeśli rzeczywista wartość nie jest ciągiem znaków, nie pojawia się błąd, co może prowadzić do błędów w czasie wykonania. Jak pokazano w poniższym kodzie, w pierwszej kolejności wybieraj bezpieczne sprawdzanie za pomocą osłon typów (type guards).
1// Good
2function isString(value: unknown): value is string {
3 return typeof value === "string";
4}
5
6if (isString(input)) {
7 console.log(input.toUpperCase());
8}Osłony typów to mechanizm pozwalający bezpiecznie określić typ podczas sprawdzania faktycznej wartości. Przedkładając osłony typów nad asercje typów, łatwiej możesz zapobiec błędom w czasie wykonania.
Nie polegaj zbytnio na inferencji typów dla typów zwracanych.
Następnie przyjrzyjmy się kwestii 'nie polegania zbytnio na inferencji typów zwracanych przez funkcje'.
Inferencja typów w TypeScript jest potężna, ale bezpieczniej jest jawnie oznaczać typy zwracane przez funkcje publiczne. To minimalizuje potencjalny wpływ przyszłych zmian.
1// Bad
2function sum(a: number, b: number) {
3 return a + b;
4}Wyrażaj zamiar jasno nawet w małych funkcjach.
1// Good
2function sum(a: number, b: number): number {
3 return a + b;
4}Deklarowanie typów zwracanych zwiększa stabilność Twojego API.
Zabezpieczaj wejścia, używając unknown.
Następnie przyjrzyjmy się kwestii 'bezpiecznego przyjmowania zewnętrznych danych wejściowych przy użyciu typu unknown'.
Dla zewnętrznych danych wejściowych, takich jak API, JSON czy dane od użytkownika, używaj unknown zamiast any. Dzięki temu masz pewność, że wszystkie wartości są zweryfikowane, co pozwala utrzymać bezpieczeństwo typów.
1// Bad
2function handleResponse(data: any) {
3 console.log(data.id);
4}Oto jak można walidować typy przy użyciu unknown.
1// Good
2function handleResponse(data: unknown): void {
3 if (
4 typeof data === "object" &&
5 data !== null &&
6 "id" in data
7 ) {
8 console.log((data as { id: number }).id);
9 }
10}unknown nie może być używany bezpośrednio; jest to typ wymagający walidacji. Jest to szczególnie skuteczne podczas obsługi zewnętrznych danych wejściowych.
Zwiększ ekspresję, łącząc małe typy.
Następnie przyjrzyjmy się kwestii 'zwiększania ekspresywności poprzez łączenie małych typów'.
Definiowanie dużych typów jednocześnie utrudnia czytelność i utrzymanie kodu. Dziel swoje typy na znaczące jednostki i łącz je w razie potrzeby.
1type Id = number;
2
3type UserProfile = {
4 id: Id;
5 name: string;
6};
7
8type UserWithStatus = UserProfile & {
9 status: "active" | "inactive";
10};Rozpatrywanie typów jako komponentów pomaga zorganizować projekt.
type i interface
Zalety interface
type i interface mogą definiować typy, ale różnią się przeznaczeniem i cechami. Stosując je zgodnie z przeznaczeniem, zamiar Twoich definicji typów staje się jasny.
1// Bad
2type User = {
3 id: number;
4 name: string;
5};
6
7type AdminUser = {
8 id: number;
9 name: string;
10 role: "admin";
11};Powielanie wspólnych części w taki sposób sprawia, że Twój kod jest podatny na zmiany.
1// Good
2interface User {
3 id: number;
4 name: string;
5}
6
7interface AdminUser extends User {
8 role: "admin";
9}interface jest idealny dla projektów z rozszerzaniem (extends) i najlepiej nadaje się do wyrażania 'kształtu' obiektów.
Zalety type
Z drugiej strony, type jest bardziej ekspresywny i nadaje się do obsługi typów sum i przecięć.
1// Good
2type Status = "idle" | "loading" | "success" | "error";
3
4type ApiResponse<T> =
5 | { status: "success"; data: T }
6 | { status: "error"; message: string };type bardzo dobrze nadaje się do wyrażania stanów, opcji i kombinacji.
Wytyczne dotyczące wyboru między type a interface
Zasadniczo używaj interface do struktur obiektów i kontraktów, a type, gdy potrzebujesz ekspresji typów sum, przecięć lub operacji na typach.
Oba działają podobnie, ale ważne jest, by wybierać je zgodnie z tym, co najlepiej oddaje powód istnienia danego typu.
Traktuj swoje typy jako dokumentację.
Na koniec przyjrzyjmy się kwestii 'zapisywania typów jako dokumentacji'.
Dobre definicje typów przekazują więcej informacji niż jakiekolwiek komentarze. Ważne jest, aby dążyć do stanu, w którym 'specyfikacja jest zrozumiała już poprzez same typy'.
1type ApiError = {
2 code: number;
3 message: string;
4 retryable: boolean;
5};W ten sposób jedną z głównych zalet TypeScript jest to, że definicje typów mogą pełnić rolę swego rodzaju dokumentacji specyfikacji.
Podsumowanie
Najlepsze praktyki TypeScript to nie nadmierna ścisłość. Istotą jest precyzowanie zamiaru przez typy i pisanie kodu odpornego na zmiany.
Gromadząc małe zasady w codziennej pracy, osiągniesz długofalowe efekty, takie jak 'łatwiejsze przeglądy', 'mniej błędów' i 'lepsze zrozumienie dla siebie w przyszłości oraz innych'.
Najpierw myśląc 'jak mogę to wyrazić za pomocą typów?', będziesz pisać wysokiej jakości kod zgodny ze stylem TypeScript.
Możesz śledzić ten artykuł, korzystając z Visual Studio Code na naszym kanale YouTube. Proszę również sprawdzić nasz kanał YouTube.