W maju pisałem o Vue.js. React to drugi popularny wybór przy projektach headless Magento i PWA. Różni się od Vue filozoficznie – mniej „magii”, więcej jawnego JavaScriptu, JSX zamiast szablonów HTML. Dla PHP developera React wymaga przestawienia myślenia, ale gdy już kliknie – jest bardzo przewidywalny. Pokazuję podstawy z perspektywy kogoś kto na co dzień pisze PHP.
React vs Vue – główna różnica filozoficzna
Vue stara się być bliski HTML – szablony wyglądają jak rozszerzony HTML z dyrektywami. React idzie w przeciwnym kierunku – wszystko jest JavaScript, a HTML piszesz jako JSX (JavaScript XML) bezpośrednio w kodzie JS. Na początku wygląda to dziwnie, ale ma sens gdy myślisz o komponencie jako o funkcji zwracającej UI:
// Vue - szablon oddzielony od logiki
// <template>
// <div>{{ product.name }}</div>
// </template>
// React - JSX bezpośrednio w JS
function ProductName({ product }) {
return <div>{product.name}</div>;
}
// Pod spodem JSX to wywołanie funkcji - to jest prawdziwy JS
// React.createElement('div', null, product.name)
Komponent funkcyjny – podstawowa jednostka React
import { useState } from 'react';
// Komponent to funkcja przyjmująca props i zwracająca JSX
function ProductCard({ product, onAddToCart }) {
// useState - lokalny stan komponentu
// [wartość, funkcja_zmiany] = useState(wartość_początkowa)
const [quantity, setQuantity] = useState(1);
const [addedToCart, setAddedToCart] = useState(false);
// Obliczona wartość - odpowiednik computed z Vue
const totalPrice = (product.price * quantity).toFixed(2);
// Handler - funkcja obsługująca zdarzenie
function handleAddToCart() {
onAddToCart({ productId: product.id, quantity });
setAddedToCart(true);
}
// JSX - HTML z wyrażeniami JS w klamrach {}
return (
<div className="product-card">
<h3>{product.name}</h3>
<p className="price">{totalPrice} PLN</p>
<div className="quantity-control">
<button
onClick={() => setQuantity(q => Math.max(1, q - 1))}
disabled={quantity <= 1}
>
-
</button>
<span>{quantity}</span>
<button onClick={() => setQuantity(q => q + 1)}>
+
</button>
</div>
<button
onClick={handleAddToCart}
className={addedToCart ? 'btn-success' : 'btn-primary'}
>
{addedToCart ? 'Dodano!' : 'Dodaj do koszyka'}
</button>
</div>
);
}
Hooks – useState i useEffect
Hooks to funkcje które „wpinają” się w mechanizmy React. Dwa najważniejsze:
useState– lokalny stan komponentuuseEffect– efekty uboczne (fetch danych, subskrypcje, timery)
import { useState, useEffect } from 'react';
function ProductList() {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [page, setPage] = useState(1);
// useEffect - uruchamia się po renderze
// Tablica zależności [page] - uruchamia się gdy zmieni się page
useEffect(() => {
let cancelled = false; // obsługa race condition
async function fetchProducts() {
setLoading(true);
setError(null);
try {
const response = await fetch(
`/rest/V1/products?searchCriteria[pageSize]=12&searchCriteria[currentPage]=${page}`
);
if (!response.ok) {
throw new Error('Błąd API: ' + response.status);
}
const data = await response.json();
// Ustaw stan tylko jeśli komponent nadal zamontowany
if (!cancelled) {
setProducts(data.items);
}
} catch (err) {
if (!cancelled) {
setError(err.message);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
}
fetchProducts();
// Funkcja czyszcząca - uruchamia się przed kolejnym efektem
return () => {
cancelled = true;
};
}, [page]); // zależność - uruchom efekt ponownie gdy page się zmieni
if (loading) return <div className="loading">Ładowanie...</div>;
if (error) return <div className="error">Błąd: {error}</div>;
return (
<div>
<div className="product-grid">
{products.map(product => (
// key - wymagany przez React przy renderowaniu list
<ProductCard
key={product.id}
product={product}
onAddToCart={handleAddToCart}
/>
))}
</div>
<div className="pagination">
<button onClick={() => setPage(p => p - 1)} disabled={page === 1}>
Poprzednia
</button>
<span>Strona {page}</span>
<button onClick={() => setPage(p => p + 1)}>
Następna
</button>
</div>
</div>
);
}
Custom Hook – wyciąganie logiki poza komponent
Powtarzalna logika (fetch, walidacja, localStorage) trafia do custom hooka – to odpowiednik prywatnej metody klasy w PHP:
// Custom hook - konwencja: nazwa zaczyna się od "use"
function useProducts(page = 1) {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
fetch(`/rest/V1/products?searchCriteria[currentPage]=${page}`)
.then(r => r.json())
.then(data => {
if (!cancelled) {
setProducts(data.items);
setLoading(false);
}
})
.catch(err => {
if (!cancelled) {
setError(err.message);
setLoading(false);
}
});
return () => { cancelled = true; };
}, [page]);
// Hook zwraca dane i stan - komponent korzysta z destrukturyzacji
return { products, loading, error };
}
// Użycie custom hooka w komponencie - czytelny, bez logiki fetchu
function ProductList() {
const [page, setPage] = useState(1);
const { products, loading, error } = useProducts(page);
if (loading) return <div>Ładowanie...</div>;
if (error) return <div>Błąd: {error}</div>;
return (
<div>
{products.map(p => <ProductCard key={p.id} product={p} />)}
</div>
);
}
React vs Vue – szybkie porównanie dla PHP developera
| Aspekt | React | Vue |
|---|---|---|
| Szablony | JSX w JS | HTML z dyrektywami |
| Stan | useState hook | data() lub ref() |
| Efekty | useEffect | onMounted, watch |
| Krzywa uczenia | Stroma (JSX, hooks) | Łagodna |
| Ekosystem | Bardzo duży (Facebook) | Duży |
| Magento PWA | PWA Studio (oficjalny) | Vue Storefront |
Podsumowanie
React wymaga więcej inwestycji na początku niż Vue – JSX i hooks są mniej intuicyjne dla kogoś przyzwyczajonego do szablonów. Ale gdy już zrozumiesz że komponent to funkcja, stan to dane które wywołują re-render, a efekty to wszystko co dzieje się „poza renderem” – React staje się bardzo przewidywalny. Jeśli planujesz pracę z Magento PWA Studio (oparty na React) – warto poświęcić czas na opanowanie podstaw hooks.
