Podstawy TypeScript: Praktyczny przewodnik dla programistów JavaScript
TypeScript dla programistów JavaScript – praktyka: podstawowe typy, tsconfig, tryb ścisły oraz krok po kroku ścieżka migracji, która sprawia, że kod jest bezpieczniejszy do modyfikacji w przyszłości.
Źródła: TypeScript for JavaScript Developers: A Practical Beginner’s Guide
Jeśli już znasz JavaScript, nauka TypeScript wydaje się mniej podobna do uczenia się zupełnie nowego języka, a bardziej do rozbudowy tego, które już rozumiesz.
TypeScript to w istocie JavaScript wzbogacony o system typów dodany na górze.
Nadal pracujesz z zmiennymi, funkcjami, tablicami, obiektami, klasami, obietnicami (promises), strukturami async/await, modułami oraz praktycznie wszystkim innym, co jest ci już znane. TypeScript oferuje zestaw narzędzi, które pomagają wykrywać błędy na wczesnym etapie, zanim twój kod zostanie uruchomiony w przeglądarce lub na serwerze.
To przewodnik omawia koncepcje TypeScript najbardziej istotne dla programistów, którzy już piszą w JavaScript.
Czym jest TypeScript?
TypeScript to język dodany na górze JavaScript.
W większości przypadków każdy program napisany w JavaScript jest również ważnym plikiem TypeScript. To, co dodaje TypeScript, to statyczna weryfikacja typów, a ostatecznie kompiluje się on do zwykłego JavaScriptu, który działa w przeglądarce i na Node.js.
Weźmy jako przykład ten prosty fragment kodu JavaScript:
function add(a, b) {
return a + b;
}
Przepisana na TypeScript ta sama funkcja może określić, jakie typy wartości przyjmuje:
function add(a: number, b: number): number {
return a + b;
}
Rozbierając to na części:
a: numberwymaga, abyabyła liczbą.b: numberwymaga, abybbyła liczbą.- Zakończenie
: numberoznacza, że wartość zwracana przez funkcję jest liczbą.
Dzięki tym adnotacjom TypeScript może zaznaczyć błędne wezwanie funkcji jeszcze przed rozpoczęciem jej wykonywania:
add(10, 20); // Correct
add("10", "20"); // TypeScript error
W zwykłym JavaScriptu wywołanie funkcji z argumentami typu string powodowałoby ciche zwrócenie wartości "1020", ponieważ operator plus łączyłby ciągi znaków zamiast dodawać liczby. TypeScript wykrywa ten rodzaj niezamierzonego zachowania z góry.
Dlaczego używać TypeScript?
Elastyczność JavaScriptu jest jego zaletą, ale może też prowadzić do subtelnych błędów.
Weźmy jako przykład tę funkcję:
function calculateDiscount(price, percentage) {
return price - price * percentage / 100;
}
Poprawne wywołanie działa zgodnie z oczekiwaniami:
calculateDiscount(1000, 10);
Ale nic nie stoi na przeszkodzie, by wywołać ją w ten sposób zamiast:
calculateDiscount("1000", "ten");
Problem pojawia się dopiero wtedy, gdy kod faktycznie zostanie wykonywany.
Za pomocą TypeScript można określić typy, których oczekuje funkcja:
function calculateDiscount(
price: number,
percentage: number
): number {
return price - price * percentage / 100;
}
Teraz TypeScript natychmiast sygnalizuje nieprawidłowe argumenty, bez konieczności najpierw uruchomienia kodu.
Do głównych zalet tego należą:
- Wykrywanie błędów podczas pisania kodu
- Lepsze wsparcie dla autodopowiadania w edytorze
- Prostsze i bezpieczniejsze refaktoryzowanie
- Jasniejszy kształt funkcji i obiektów
- Lepsza współpraca przy większych bazach kodu
- Typy pełniące rolę wbudowanej dokumentacji
Znaczenie TypeScript staje się szczególnie oczywiste, gdy projekt przekracza kilka plików lub angażuje więcej niż jednego twórcę.
Instalacja TypeScript
TypeScript można zainstalować globalnie za pomocą:
npm install -g typescript
Aby potwierdzić zainstalowaną wersję, uruchom:
tsc --version
Następnie utwórz plik o nazwie:
app.ts
Dodaj do niego trochę kodu w TypeScript:
const message: string = "Hello TypeScript";
console.log(message);
Zkompiluj plik za pomocą:
tsc app.ts
To daje skompilowany wynik w formacie JavaScript:
app.js
Możesz uruchomić wynik bezpośrednio za pomocą Node.js:
node app.js
W środowiskach produkcyjnych narzędzia i frameworki takie jak React, Next.js, Angular oraz NestJS zazwyczaj automatycznie zajmują się tym krokiem kompilacji.
Plik tsconfig.json
Większość projektów TypeScript polega na pliku tsconfig.json, aby skonfigurować kompilator.
Możesz go stworzyć za pomocą:
tsc --init
Minimalna konfiguracja może wyglądać w ten sposób:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"outDir": "./dist",
"rootDir": "./src"
}
}
Oto kilka najważniejszych ustawień:
target
Kontroluje to, jaką wersję JavaScriptu wyświetla kompilator.
"target": "ES2020"
module
Określa format modułu używany w wyniku kompilacji.
"module": "ESNext"
strict
Włączenie tej opcji aktywuje bardziej rygorystyczny poziom sprawdzania typów.
"strict": true
Dla każdego nowego projektu zdecydowanie zaleca się pozostawienie trybu rygorystycznego włączonego.
rootDir
To informuje kompilator o miejscu, w którym znajdują się pliki źródłowe TypeScript.
"rootDir": "./src"
outDir
To określa miejsce, w którym powinny być zapisywane skompilowane pliki JavaScript.
"outDir": "./dist"
Podstawowe typy
TypeScript zawiera kilka typów prymitywnych, które zapewne już znasz.
String
let username: string = "Vijeet";
Number
let experience: number = 10;
TypeScript nie rozróżnia liczb całkowitych od liczb zmiennoprzecinkowych; obie należą do typu number.
let age: number = 35;
let price: number = 999.99;
Boolean
let isActive: boolean = true;
Null i Undefined
let emptyValue: null = null;
let missingValue: undefined = undefined;
W trybie ścisłym będziesz musiał bardziej starannie radzić sobie z wartościami null i undefined.
Wynikanie typów
Nie musisz ręcznie adnotować każdej wartości.
TypeScript często potrafi określić typ zmiennej na podstawie tego, co do niej zostanie przypisane.
let city = "Ahmedabad";
Tutaj TypeScript automatycznie rozpoznaje city jako ciąg znaków.
Próba ponownego przypisania tej zmiennej w ten sposób spowoduje błąd:
city = 100;
Ta sama logika ma zastosowanie tutaj:
let total = 500;
TypeScript wnioskuje, że total przechowuje liczbę.
Poleganie na wynikaniu typów sprawia, że kod jest mniej zagracony. Zachowaj wyraźne adnotacje typów tam, gdzie naprawdę dodają one klarowności, np. w parametrach funkcji, strukturach odpowiedzi API oraz bardziej złożonych strukturach obiektów.
Masywy
Oto zwykły masał w JavaScript:
const skills = ["JavaScript", "React", "Node.js"];
TypeScript umożliwia adnotowanie typu elementów tablicy:
const skills: string[] = [
"JavaScript",
"React",
"Node.js"
];
Można to również zapisać za pomocą składni generycznej:
const skills: Array<string> = [
"JavaScript",
"React",
"Node.js"
];
Ta sama zasada dotyczy tablic liczbowych:
const scores: number[] = [75, 82, 91];
TypeScript blokuje wszelkie próby wstawienia wartości niewłaściwego typu:
scores.push(95); // Correct
scores.push("ninety"); // Error
Obiekty
Można nadać zwykłemu obiektowi JavaScriptu wyraźny kształt.
const user: {
name: string;
age: number;
isAdmin: boolean;
} = {
name: "Vijeet",
age: 35,
isAdmin: true
};
Jeśli właściwość jest brakująca lub ma niewłaściwy typ, TypeScript sygnalizuje to jako błąd.
const user: {
name: string;
age: number;
} = {
name: "Vijeet"
};
Tutaj wymagana właściwość age brakuje w obiekcie.
Gdy potrzebujesz ponownego użycia tego samego kształtu obiektu w kilku miejscach, interfejsy lub aliasy typów są lepszym rozwiązaniem niż pisanie struktury za każdym razem.
Interfejsy
Interfejs definiuje kształt, jaki powinien mieć obiekt.
interface User {
id: number;
name: string;
email: string;
isActive: boolean;
}
Gdy zostanie zdefiniowana, można ją ponownie wykorzystać w dowolnym miejscu:
const user: User = {
id: 1,
name: "Vijeet",
email: "vijeet@example.com",
isActive: true
};
Interfejsy są przydatne w sytuacjach takich jak:
- opisywanie przechowywanych danych użytkownika
- modelowanie pozycji z katalogu
- weryfikacja pól wprowadzonych w formularzu
- kształtowanie treści zwracanej po wywołaniu API
- określanie typów parametrów przekazywanych do komponentu
- reprezentowanie wiersza z tabeli bazy danych
Naprzимер:
interface Product {
id: number;
title: string;
price: number;
}
Funkcja może wtedy przyjmować Product jako parametr:
function displayProduct(product: Product): void {
console.log(`${product.title}: ₹${product.price}`);
}
Atrybuty opcjonalne
Część atrybutów obiektu nie zawsze będzie obecna.
Oznacza się je, dodając znak zapytania po nazwie atrybutu:
interface User {
id: number;
name: string;
phone?: string;
}
Oba poniższe obiekty spełniają wymagania interfejsu:
const userOne: User = {
id: 1,
name: "Amit"
};
const userTwo: User = {
id: 2,
name: "Neha",
phone: "9876543210"
};
Ponieważ phone jest opcjonalny, jego typ staje się:
string | undefined
To oznacza, że powinieneś sprawdzić, czy istnieje, zanim go użyjesz:
if (userTwo.phone) {
console.log(userTwo.phone);
}
Aliasy typów
Alias typu pozwala przypisać ponownie używalną nazwę dowolnemu typowi.
type UserId = number;
Następnie możesz używać tego aliasu jako typu:
const id: UserId = 101;
Aliasy typów szczególnie przydają się przy opisywaniu związków lub bardziej złożonych kombinacji typów.
type Status = "pending" | "approved" | "rejected";
Przy użyciu tego aliasu dozwolone są tylko trzy konkretne łańcuchy znaków:
let applicationStatus: Status = "pending";
Próba użycia czegokolwiek innego powoduje błąd:
applicationStatus = "completed";
Interfejsy vs aliasy typów
Zarówno interfejsy, jak i aliasy typów mogą modelować strukturę obiektu.
Oto wersja z interfejsem:
interface Customer {
name: string;
email: string;
}
A odpowiednia wersja z aliasem typu:
type Customer = {
name: string;
email: string;
};
Jako ogólne wytyczne dla początkujących:
- Używaj
interface, gdy opisujesz obiekty lub strukturę klas. - Używaj
type, gdy pracujesz z uniami, aliasami dla prymitywów, tuplami lub bardziej złożonymi typami połączonymi.
To jednak nie jest sztywna zasada – w wielu przypadkach obie metody działają równie dobrze.
Typy unii
Typ unii pozwala wartości przybrać jeden z kilku możliwych typów.
let userId: string | number;
Oba te przypisania są dopuszczalne:
userId = 101;
userId = "USR-101";
Typy unii są przydatne, gdy dane pochodzące ze świata rzeczywistego nie zawsze spełniają określony format.
function printId(id: string | number): void {
console.log(id);
}
Zanim wywołasz metodę dostępną tylko dla jednego z możliwych typów, musisz najpierw zawęzić zakres wartości:
function printId(id: string | number): void {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id.toFixed(0));
}
}
Ta technika nazywana jest zawężaniem typu.
Typy literałowe
Typy literackie ograniczają zmienną do dokładnego, z góry określonego zbioru wartości.
type Theme = "light" | "dark" | "system";
To przypisanie jest w porządku:
let selectedTheme: Theme = "dark";
Ale to zostaje odrzucone:
selectedTheme = "blue";
Typy literackie doskonale pasują do przypadków takich jak:
- bieżący stan rekordu
- pilotaż uprawnień przypisanych użytkownikowi
- ustawienie tematu wizualnego
- styl wizualny przycisku
- czasownik HTTP używany w żądaniu
- tryb, w którym działa aplikacja
Jako przykład:
type UserRole = "admin" | "editor" | "customer";
Funkcje
Dla parametrów funkcji typ znajduje się bezpośrednio po nazwie parametru.
function greet(name: string): string {
return `Hello, ${name}`;
}
Typ zwracany przez funkcję znajduje się po zamkniętej nawiasie listy parametrów.
function multiply(a: number, b: number): number {
return a * b;
}
Gdy funkcja nie zwraca wartości, należy ją oznaczyć słowem void.
function logMessage(message: string): void {
console.log(message);
}
Parametry opcjonalne są oznaczane symbolem ?:
function greetUser(
name: string,
title?: string
): string {
if (title) {
return `Hello, ${title} ${name}`;
}
return `Hello, ${name}`;
}
Wartości domyślne parametrów zachowują się tak samo jak w zwykłym JavaScriptu:
function calculateTax(
amount: number,
rate: number = 18
): number {
return amount * rate / 100;
}
Typowanie funkcji strzałkowych
Funkcje strzałkowe można anotować w ten sam sposób.
const subtract = (
a: number,
b: number
): number => {
return a - b;
};
Oto bardziej zwięzła forma:
const subtract = (
a: number,
b: number
): number => a - b;
Typ any
Przypisanie typu any do wartości informuje TypeScript, aby całkowicie przestał ją sprawdzać.
let data: any = "Hello";
data = 100;
data = true;
data = { name: "Vijeet" };
Chociaż na początku może to wydawać się wygodne, zbyt częste używanie any oznacza utratę większości zalet oferowanych przez TypeScript.
Weźmy ten przykład:
function processData(data: any): any {
return data.someProperty.someMethod();
}
TypeScript nie może nic zrobić, aby wykryć błędy w tej funkcji.
Zarezerwuj any na sytuacje, gdy jest to naprawdę nieuniknione — na przykład podczas stopniowej konwersji bazy kodu JavaScript lub gdy mamy do czynienia z danymi od dostawców trzecich, które nie zawierają odpowiednich definicji typów.
Typ unknown
unknown oferuje bezpieczniejszy sposób reprezentowania wartości, których typ nie jest znany z góry.
let value: unknown;
Tak jak any, przyjmuje on wszelkiego rodzaju wartości:
value = "Hello";
value = 100;
value = true;
Jednak w odróżnieniu od any, TypeScript zmusza do zawężenia typu przed jego faktycznym użyciem:
if (typeof value === "string") {
console.log(value.toUpperCase());
}
Dzięki temu unknown jest odpowiedni dla:
- Danych pochodzących z zewnętrznych API
- Wartości parsowanych z JSON
- Obiektów błędów złapanych w bloku try/catch
- Danej wprowadzonej przez użytkowników
- Czegokolwiek, którego struktura nie została jeszcze określona
Aksjunkcje typów
Czasami masz więcej informacji na temat wartości, niż TypeScript może samodzielnie wywnioskować.
Aksjacja typu pozwala ci poinformować kompilator, jak ma traktować tę wartość.
const input = document.getElementById(
"email"
) as HTMLInputElement;
Po dokonaniu aksjacji możesz uzyskać dostęp do właściwości specyficznych dla tego typu:
console.log(input.value);
Pamiętaj, że aksjacja nic nie zmienia w czasie wykonywania — zmienia jedynie sposób, w jaki TypeScript interpretuje wartość podczas kompilacji.
Z tego powodu należy używać aksjacji oszczędnie i tylko wtedy, gdy jesteś pewien rzeczywistej struktury danych.
Tuple
Tuple to tablica z ustaloną liczbą pozycji, przy czym każda pozycja ma swoją określoną typowość.
let userEntry: [number, string];
userEntry = [1, "Vijeet"];
Tutaj pierwsza pozycja musi zawierać liczbę, a druga ciąg znaków.
Poniższy przypadek spowoduje błąd:
userEntry = ["Vijeet", 1];
Wiązki doskonale sprawdzają się przy małych danych o stałej strukturze lub parach wartości:
type Coordinate = [number, number];
const location: Coordinate = [23.0225, 72.5714];
Gdy dane stają się bardziej złożone lub wymagają opisowych nazw pól, zwykłe obiekty są zazwyczaj jaśniejsze w interpretacji.
Enumy
Enum umożliwia definiowanie ustalonej grupy stałych o nazwach.
enum OrderStatus {
Pending,
Processing,
Shipped,
Delivered
}
Można do nich odwoływać się w ten sposób:
let status: OrderStatus = OrderStatus.Processing;
Przypisanie enumom wartości typu string często poprawia czytelność kodu:
enum PaymentStatus {
Pending = "pending",
Paid = "paid",
Failed = "failed"
}
Mimo to wielu użytkowników TypeScript preferuje typy union, szczególnie w prostszych scenariuszach:
type PaymentStatus =
| "pending"
| "paid"
| "failed";
Typy union zazwyczaj składają się na mniej kodu JavaScript i stanowią solidny standard dla większości kodu front-endowego.
Klasy
TypeScript rozszerza zwykłe klasy JavaScript o funkcje sprawdzania typów.
class Employee {
name: string;
salary: number;
constructor(name: string, salary: number) {
this.name = name;
this.salary = salary;
} getAnnualSalary(): number {
return this.salary * 12;
}
}
Tworzenie instancji klasy wygląda tak samo jak w JavaScript:
const employee = new Employee("Rahul", 50000);
console.log(employee.getAnnualSalary());
Modyfikatory dostępu są również częścią składni klas w TypeScript.
class BankAccount {
public accountHolder: string;
private balance: number;
constructor(accountHolder: string, balance: number) {
this.accountHolder = accountHolder;
this.balance = balance;
} public getBalance(): number {
return this.balance;
}
}
Główne modyfikatory to:
public: widoczny z każdego miejscaprivate: widoczny tylko wewnątrz samej klasyprotected: widoczny wewnątrz klasy oraz jej podklasreadonly: można go ustawić raz, ale nie przypisać ponownie później
Generyki
Generyki umożliwiają pisanie elastycznego, wielokrotnie używalnego kodu, który zachowuje pełne bezpieczeństwo typów przy różnych typach danych wejściowych.
Weź tę funkcję jako punkt wyjścia:
function returnValue(value: any): any {
return value;
}
Ponieważ polega ona na any, TypeScript nie może podać żadnych informacji na temat tego, co z niej wynika.
Zmiana kodu przy użyciu parametru ogólnego rozwiązuje ten problem:
function returnValue<T>(value: T): T {
return value;
}
Teraz typ zwracany jest zgodny z tym, co zostało przekazane:
const text = returnValue<string>("Hello");
const count = returnValue<number>(100);
W wielu przypadkach TypeScript może automatycznie wywnioskować typ ogólny, więc nie musisz go ręcznie określać:
const text = returnValue("Hello");
const count = returnValue(100);
Genery pojawiają się ciągle podczas pracy z tablicami, strukturami odpowiedzi API, komponentami interfejsu użytkownika wielokrotnie używalnymi, własnymi hookami oraz narzędziami pomocniczymi.
function getFirstItem<T>(items: T[]): T | undefined {
return items[0];
}
Używane w ten sposób:
const firstNumber = getFirstItem([10, 20, 30]);
const firstName = getFirstItem(["Amit", "Neha"]);
Promesy i funkcje asynchroniczne
Funkcje deklarowane z pomocą async zawsze zwracają promesy.
async function getUsername(): Promise<string> {
return "Vijeet";
}
Funkcja asynchroniczna, która zwraca obiekt użytkownika, może wyglądać w ten sposób:
interface User {
id: number;
name: string;
}
async function getUser(): Promise<User> {
return {
id: 1,
name: "Vijeet"
};
}
A dla listy użytkowników typ zwracany musi być umieszczony w tablicy:
async function getUsers(): Promise<User[]> {
return [
{ id: 1, name: "Vijeet" },
{ id: 2, name: "Rahul" }
];
}
Przypisanie odpowiednim typom wyników API w ten sposób chroni resztę kodu przed błędami późniejszego etapu implementacji.
Określanie typów odpowiedzi API
Załóżmy endpoint, który zwraca rekordy produktów:
interface Product {
id: number;
title: string;
price: number;
}
Można otoczyć wywołanie fetch funkcją, która deklaruje jej typ zwracany:
async function fetchProducts(): Promise<Product[]> {
const response = await fetch("/api/products");
if (!response.ok) {
throw new Error("Unable to fetch products");
} const data: Product[] = await response.json(); return data;
}
Warto pamiętać o jednym ograniczeniu: TypeScript nie ma możliwości weryfikacji tych danych po uruchomieniu aplikacji.
Ta linia:
const data: Product[] = await response.json();
jedynie poleca kompilatorowi traktowanie zinterpretowanej odpowiedzi jako Product[] — nie potwierdza, że serwer rzeczywiście odesłał obiekty w takiej formie.
W aplikacjach, gdzie ważna jest poprawność, programiści często łączą TypeScript z biblioteką walidacji w czasie wykonywania, taką jak Zod, aby zrekompensować to ograniczenie.
Obsługa elementów DOM
Rodzaje elementów przeglądarki są wbudowane w TypeScript.
const button = document.querySelector(
"#submit-button"
);
Kompilator wie, że ta zapytanie może zwrócić pustą wartość, dlatego typ wyniku obejmuje null obok typu elementu.
To oznacza, że powinieneś zabezpieczyć się przed brakiem danych przed ich użyciem:
if (button) {
button.addEventListener("click", () => {
console.log("Button clicked");
});
}
Jeśli potrzebujesz bardziej specyficznego typu elementu, możesz to potwierdzić:
const emailInput = document.querySelector(
"#email"
) as HTMLInputElement | null;
Następnie odczytaj jego wartość po potwierdzeniu, że istnieje:
if (emailInput) {
console.log(emailInput.value);
}
TypeScript nie zastępuje walidacji w czasie wykonywania
To zagadnienie wymaga podkreślenia, szczególnie dla programistów przechodzących bezpośrednio z JavaScript.
Kontrole w TypeScript odbywają się tylko w czasie rozwoju i kompilacji. Gdy twój kod zostanie skompilowany do JavaScript, wszystkie adnotacje typowe znikają.
Weźmy tę funkcję:
function double(value: number): number {
return value * 2;
}
Po kompilacji wygląda ona mniej więcej tak:
function double(value) {
return value * 2;
}
W tym momencie nie pozostał żaden ślad oryginalnych informacji typowych, które mogłyby coś zagwarantować.
W rezultacie TypeScript nie może zagwarantować, że:
- API faktycznie zwróciło poprawnie sformatowane dane
- Użytkownik wpisał ważną wartość
- Zapis w bazie danych odpowiada oczekiwanej strukturze
- Służba third-party dotrzymała swojego udokumentowanego kontraktu
Wszystko, co pochodzi spoza twojego kodu, nadal musi być sprawdzane podczas działania programu.
Migracja istniejącego projektu JavaScript
Konwersja bazy kodu JavaScript na TypeScript nie musi odbywać się za jednym razem poprzez całkowite przepisanie.
Migracja krok po kroku zazwyczaj lepiej działa w praktyce.
Krok 1: Zainstaluj TypeScript
npm install --save-dev typescript
Krok 2: Stwórz plik tsconfig.json
npx tsc --init
Krok 3: Tymczasowo zezwalaj na JavaScript
{
"compilerOptions": {
"allowJs": true,
"checkJs": false
}
}
Krok 4: Stopniowe przemianowanie plików
Zmień plik w ten sposób:
utils.js
na:
utils.ts
A w przypadku komponentów React zamień:
Button.jsx
na:
Button.tsx
Krok 5: Naprawianie błędów po jednym pliku
Pomaga zacząć od funkcji pomocniczych, wspólnych modeli danych oraz mniejszych komponentów, zachowując bardziej skomplikowaną logikę aplikacji na później.
Ta stopniowa metoda to jeden z największych praktycznych zalet TypeScript przy wdrażaniu go w istniejącym projekcie.
Częste błędy popełniane przez programistów JavaScript
Używanie any wszędzie
const user: any = getUser();
To może ukryć natychmiastową skargę kompilatora, ale jednocześnie usuwa całą weryfikację typów.
Lepszym podejściem jest zdefiniowanie rzeczywistej interfejsu:
interface User {
id: number;
name: string;
}
Dodawanie typów do każdej zmiennej
Nie ma potrzeby pisania tego:
const name: string = "Vijeet";
const count: number = 10;
const active: boolean = true;
ponieważ inferencja typów sama ustala te typy:
const name = "Vijeet";
const count = 10;
const active = true;
Zachowaj anotacje eksplikite tylko w tych miejscach, gdzie rzeczywiście zwiększają one klarowność.
Ignowowanie null
Ten kod może ulec błędowi w czasie wykonywania:
const element = document.querySelector("#app");
element.innerHTML = "Hello";
Element, który jest wyszukiwany, może po prostu nie istnieć na stronie.
Lepiej zabezpiecz się przed taką możliwością:
const element = document.querySelector("#app");
if (element) {
element.innerHTML = "Hello";
}
Używanie asercji typu do ukrywania problemów
Pisanie czegoś takiego jest ryzykowne:
const user = apiResponse as User;
Asercja nie sprawdza niczego dotyczącego rzeczywistej odpowiedzi API — po prostu mówi kompilatorowi, by uwierzył w twoje słowa.
Traktowanie TypeScript jako innego środowiska wykonywania
TypeScript nie jest wykonywany bezpośrednio przez przeglądarkę. Zanim zostanie uruchomiony, jest kompilowany do zwykłego JavaScriptu.
To oznacza, że wszystko, co już wiesz o JavaScriptu, nadal ma zastosowanie.
Praktyczny przykład
Rozważmy prosty koszyk zakupowy, napisany w sposób typowy dla JavaScriptu, ale teraz z dodanymi typami TypeScriptu.
interface CartItem {
id: number;
name: string;
price: number;
quantity: number;
}
function calculateCartTotal(
items: CartItem[]
): number {
return items.reduce((total, item) => {
return total + item.price * item.quantity;
}, 0);
}
const cart: CartItem[] = [
{
id: 1,
name: "Keyboard",
price: 2500,
quantity: 1
},
{
id: 2,
name: "Mouse",
price: 1200,
quantity: 2
}
];
const total = calculateCartTotal(cart);
console.log(`Cart total: ₹${total}`);
Dzięki temu funkcja jest teraz chroniona przed błędnymi danymi w koszyku.
Naprzимер, poniższy fragment nie zostanie skompilowany:
const invalidCart: CartItem[] = [
{
id: 1,
name: "Keyboard",
price: "2500",
quantity: 1
}
];
Problem polega na tym, że price jest ustawiony jako ciąg znaków, podczas gdy powinien być liczbą.
Czy każdy projekt w JavaScriptu powinien używać TypeScriptu?
Nie zawsze.
Zwykły JavaScript nadal dobrze sprawdza się w takich przypadkach jak:
- Małe eksperymenty
- Proste skrypty
- Szybkie prototypy
- Bazowe strony startowe
TypeScript przynosi większe korzyści w sytuacjach takich jak:
- Wielkie aplikacje frontendowe
- Bazy kodu React i Next.js
- Słuzby backendowe Node.js
- Bazy kodu udostępniane między zespołami
- Produkty działające przez długi czas
- Projekty zajmujące się złożonymi strukturami danych
- Aplikacje budowane i utrzymywane przez wielu programistów
Ogólnie rzecz biorąc, im większy i dłużej trwający jest projekt, tym większe korzyści wynikają z użycia TypeScript.
Ostateczne uwagi
TypeScript nie zastępuje JavaScriptu — ulepsza doświadczenie przy pisaniu kodu w tym języku, dodając dodatkową warstwę bezpieczeństwa.
Jako osoba przychodząca z JavaScriptu, masz już większość niezbędnej wiedzy podstawowej:
- Zmienne
- Funkcje
- Obiekty
- Tablice
- Klasy
- Obietnice
- Modyuły
- Kod asynchroniczny
To, czego jeszcze trzeba się nauczyć, to głównie jak przedstawić strukturę swoich danych.
Dobry punkt wyjścia obejmuje:
- Typy prymitywne
- Typy parametrów i wartości zwracanych przez funkcje
- Interfejsy
- Typy unijne
- Atrybuty opcjonalne
- Generiki
- Odpowiedzi typowane z API
Nie próbuj od razu opanować wszystkich zaawansowanych funkcji. Zacznij od adnotowania codziennego JavaScriptu i pozwól, by potrzeby twoich rzeczywistych projektów kierowały tym, czego się dalej nauczysz.
Gdy TypeScript stanie się twoją drugą naturą, powrót do zwykłego, nietypowanego JavaScripta może wydawać się niespodziewanie niepewny.
Literatura pokrewna
- Behawiry TypeScript, które zaskakują doświadczonych programistów, i dlaczego — typowanie strukturalne, nadmierne sprawdzanie właściwości, użycie klucza as const, funkcje satisfies, typy warunkowe i mapped types, a także zasady projektowania, które przekształcają je w bezpieczniejszy kod.
- Opanuj wbudowane tipy-usługowe TypeScript dla czystszego kodu — Dowiedz się, jak tipy-usługowe TypeScript takie jak Partial, Pick, Omit i Record eliminują duplikaty interfejsów oraz automatycznie synchronizują definicje typów.