Jeśli przez lata pisałeś PHP z type hintami i declare(strict_types=1), praca z czystym JavaScriptem boli. Brak typów, brak autouzupełniania, błędy które wychodzą dopiero w przeglądarce. TypeScript rozwiązuje ten problem – dodaje do JavaScriptu system typów który PHP developer natychmiast doceni. Pokazuję podstawy z perspektywy kogoś kto myśli w PHP.
Dlaczego TypeScript, nie czysty JavaScript
PHP developer przyzwyczajony do typed properties, union types i PHPStan będzie czuł się w czystym JS jak bez rąk. TypeScript przynosi to co znamy z PHP 8.x – typy przy zmiennych, parametrach i wartościach zwracanych – ale w ekosystemie JS/Node.js:
// JavaScript - brak typów, błędy w runtime
function calculateTotal(items, taxRate) {
return items.reduce((sum, item) => sum + item.price * item.qty, 0) * (1 + taxRate);
}
calculateTotal([{price: 10, qty: 2}], 0.23); // ok: 24.6
calculateTotal("oops", 0.23); // NaN - żaden błąd przed wykonaniem
calculateTotal([{price: "10", qty: 2}], 0.23); // "1010" - ciche złączenie stringów!
// TypeScript - błędy wykryte przed wykonaniem
interface OrderItem {
price: number;
qty: number;
name: string;
}
function calculateTotal(items: OrderItem[], taxRate: number): number {
return items.reduce((sum, item) => sum + item.price * item.qty, 0) * (1 + taxRate);
}
calculateTotal([{price: 10, qty: 2, name: 'Widget'}], 0.23); // ok: 24.6
calculateTotal("oops", 0.23); // Error kompilacji!
calculateTotal([{price: "10", qty: 2, name: 'Widget'}], 0.23); // Error kompilacji!
Instalacja i konfiguracja
# Instalacja TypeScript npm install --save-dev typescript ts-node @types/node # Inicjalizacja projektu npx tsc --init # Uruchomienie pliku TS bez kompilacji (dev) npx ts-node src/index.ts # Kompilacja do JS npx tsc
// tsconfig.json - rekomendowana konfiguracja dla PHP developera
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true, // odpowiednik declare(strict_types=1)
"noImplicitAny": true, // brak niejawnego any - jak strict_types
"strictNullChecks": true, // null i undefined nie są wszędzie
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true
}
}
Podstawowe typy – analogie z PHP
// Typy podstawowe - odpowiedniki PHP let name: string = "Jan"; // string let age: number = 30; // int|float (JS ma jeden typ numeryczny) let active: boolean = true; // bool let nothing: null = null; // null let undef: undefined = undefined; // nie ma w PHP // Arrays let ids: number[] = [1, 2, 3]; // array z PHP let mixed: (string|number)[] = ['a', 1]; // union type w tablicy // Tuple - tablica o stałej długości i typach let pair: [string, number] = ['SKU-001', 29.99]; // Union types - jak PHP 8.0+ let id: string | number = 42; id = 'abc'; // też ok // Nullable - jak ?string w PHP let optionalName: string | null = null; optionalName = "Anna"; // ok // Literal types - brak w PHP, TypeScript unikalny let direction: 'asc' | 'desc' = 'asc'; direction = 'up'; // Error: Type '"up"' is not assignable
Interface i Type – odpowiednik klas i interfejsów PHP
// Interface - jak interface w PHP
interface ProductInterface {
readonly id: number;
sku: string;
name: string;
price: number;
isActive: boolean;
categories: string[];
description?: string; // opcjonalne - jak ?string w PHP
}
// Type alias - bardziej elastyczny od interface
type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled';
type Money = {
amount: number; // w groszach
currency: string;
};
// Intersection type - jak A&B w PHP 8.1
type AdminUser = ProductInterface & {
adminLevel: number;
permissions: string[];
};
// Implementacja interface - jak w PHP
class Product implements ProductInterface {
readonly id: number;
sku: string;
name: string;
price: number;
isActive: boolean;
categories: string[];
description?: string;
constructor(data: Omit<ProductInterface, 'id'> & { id?: number }) {
this.id = data.id ?? Math.random();
this.sku = data.sku;
this.name = data.name;
this.price = data.price;
this.isActive = data.isActive;
this.categories = data.categories;
this.description = data.description;
}
getFormattedPrice(): string {
return (this.price / 100).toFixed(2) + ' PLN';
}
}
Generics – jak szablony typów
// Generics - PHP nie ma odpowiednika (PHPStan templates to closest)
// Ale PHP developer zrozumie ideę z Repository pattern
interface Repository<T, ID = number> {
findById(id: ID): Promise<T | null>;
findAll(): Promise<T[]>;
save(entity: T): Promise<T>;
delete(id: ID): Promise<boolean>;
}
// Konkretna implementacja - typ T jest podstawiony przy użyciu
class ProductRepository implements Repository<ProductInterface> {
private items: Map<number, ProductInterface> = new Map();
async findById(id: number): Promise<ProductInterface | null> {
return this->items.get(id) ?? null;
}
async findAll(): Promise<ProductInterface[]> {
return Array.from(this.items.values());
}
async save(product: ProductInterface): Promise<ProductInterface> {
this.items.set(product.id, product);
return product;
}
async delete(id: number): Promise<boolean> {
return this.items.delete(id);
}
}
// Generic function - działa z dowolnym typem
function first<T>(array: T[]): T | undefined {
return array[0];
}
const firstProduct = first([{id: 1, sku: 'A', name: 'Widget', price: 100, isActive: true, categories: []}]);
// TypeScript wie że firstProduct jest ProductInterface | undefined
Async/Await – jak w PHP 8.1 Fibers, ale masowo używany
// Async/await - w JS to standard, w PHP dopiero Fibers/ReactPHP
// Dla PHP developera to jak normalne funcje, ale "nieblokujące"
interface ApiResponse {
items: ProductInterface[];
total: number;
page: number;
}
async function fetchProducts(page: number = 1): Promise<ApiResponse> {
const response = await fetch(`/api/products?page=${page}`);
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return response.json() as Promise<ApiResponse>;
}
// TypeScript wymusza obsługę Promise - nie możesz "zapomnieć" że to async
async function loadPage(): Promise<void> {
try {
const data = await fetchProducts(1);
console.log(`Załadowano ${data.items.length} z ${data.total} produktów`);
} catch (error) {
if (error instanceof Error) {
console.error(`Błąd: ${error.message}`);
}
}
}
TypeScript z Magento 2
Magento 2 używa knockout.js i RequireJS – oba mają definicje typów. Jeśli piszesz własne komponenty JS dla Magento, warto dodać TypeScript do procesu buildowania:
# Typy dla popularnych bibliotek używanych przy Magento npm install --save-dev @types/knockout @types/requirejs @types/jquery
// Komponent knockout.js z typami TypeScript
import * as ko from 'knockout';
interface CartItem {
sku: string;
name: string;
qty: ko.Observable<number>;
price: number;
}
class CartViewModel {
items: ko.ObservableArray<CartItem>;
total: ko.Computed<number>;
constructor() {
this.items = ko.observableArray<CartItem>([]);
this.total = ko.computed(() =>
this.items().reduce((sum, item) => sum + item.price * item.qty(), 0)
);
}
addItem(item: CartItem): void {
this.items.push(item);
}
removeItem(item: CartItem): void {
this.items.remove(item);
}
}
Podsumowanie
TypeScript dla PHP developera to powrót do domu po długiej wycieczce w dzikie ostępy czystego JS. System typów, interfejsy, union types, strict mode – te koncepty brzmią znajomo bo PHP 8.x poszedł podobną drogą. Kluczowa różnica to generics i asynchroniczność przez Promises – tu TypeScript idzie dalej niż PHP. Jeśli budujesz frontend dla Magento albo piszesz Node.js API – TypeScript to inwestycja która zwraca się przy pierwszym refaktoringu.
