PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

React.js – JSX, useState, useEffect, custom hooks, porównanie z Vue

by Henryk Tews / wtorek, 08 czerwca 2021 / Opublikowano w JavaScript

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 komponentu
  • useEffect – 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.

About Henryk Tews

Co możesz przeczytać następne

Vue.js dla PHP developera – Options API, Composition API, komunikacja z Magento REST
TypeScript dla PHP developera – typy, interface, generics, async/await
Next.js dla PHP developera – SSR, Server Components, GraphQL z Magento, porównanie z MVC
  • Publikacje
  • O autorze
  • Kontakt

© 2026 Created by

GÓRA
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Zawsze aktywne
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Zarządzaj opcjami
  • Zarządzaj serwisami
  • Zarządzaj {vendor_count} dostawcami
  • Przeczytaj więcej o tych celach
Zobacz preferencje
  • {title}
  • {title}
  • {title}