Obsługa stanów interfejsu użytkownika w rzeczywistym świecie za pomocą warunkowego renderowania w React
Dowiedz się, jak tworzyć interfejsy użytkownika do autoryzacji, ról, uprawnień, ładowania, błędów oraz stanu pustego w React przy użyciu praktycznych wzorców renderowania warunkowego.
W poprzednim rozdziale omówiliśmy podstawy renderowania warunkowego — wykorzystanie prostych warunków, aby poinformować React, czy ma pokazać jeden komponent, inny komponent, czy w ogóle nic.
Jednak aplikacje z rzeczywistego świata rzadko pozostają tak proste:
isLoggedIn ? <Dashboard /> : <Login />
Rozważmy prawdziwą panel kontrolny. Użytkownik może znajdować się w jednym z następujących stanów:
- Zalogowany wyłącznie
- W trakcie logowania
- Zalogowany
- Administrator
- Zwykły użytkownik
- Brakuje wymaganej uprawnień
- Czekanie na przybycie danych
- Rozwiązywanie problemu z API
- Oglądanie pustego zestawu wyników
Każdy z tych scenariuszy wymaga odrębnego interfejsu użytkownika. Właśnie w tym momencie renderowanie warunkowe przestaje być prostym trikiem i staje się prawdziwym narzędziem do strukturyzowania aplikacji.
Zobaczmy, jak to wygląda w kodzie React o poziomie produkcyjnym.
1. Renderowanie oparte na autoryzacji
Klasycznym przypadkiem zastosowania jest autoryzacja. Wyobraźmy sobie aplikację z dwoma możliwymi ekranami:
Not Logged In
↓
Login Page
Logged In
↓
Dashboard
React może łatwo wybrać między nimi:
function App() {
const isLoggedIn = true;
return (
<>
{isLoggedIn ? <Dashboard /> : <Login />}
</>
);
}
Jednak prawdziwe aplikacje zazwyczaj potrzebują trzeciego stanu: loading, ponieważ może minąć chwila, zanim aplikacja potwierdzi, czy token użytkownika jest ważny. Przepływ wtedy wygląda tak:
Checking Authentication
↓
Loading
↓
Authenticated?
↙ ↘
YES NO
↓ ↓
Dashboard Login
W kodzie może to wyglądać tak:
function App() {
const isLoading = false;
const isLoggedIn = true;
if (isLoading) {
return <LoadingSpinner />;
}
return isLoggedIn
? <Dashboard />
: <Login />;
}
Zobaczysz to dokładne wzorzec powtarzający się w niezliczonych aplikacjach React produkcyjnych.
2. Renderowanie oparte na rolach
Autoryzacja mówi ci, kto jest zalogowany. Uprawnienia mówią ci, co ta osoba może robić.
Weźmy jako przykład narzędzie do zarządzania pracownikami. Administrator może widzieć:
View Employees
Add Employee
Edit Employee
Delete Employee
Podczas gdy zwykły użytkownik widzi tylko:
View Employees
Można warunkowo wyświetlać akcje w zależności od roli użytkownika:
function EmployeeCard({ userRole }) {
return (
<div>
<h2>Employee Details</h2>
<button>View</button>
{userRole === "admin" && (
<>
<button>Edit</button>
<button>Delete</button>
</>
)}
</div>
);
}
Dzięki takiemu ustawieniu elementy dostępne tylko dla administratorów pojawiają się wyłącznie dla nich.
Ważna uwaga dotycząca bezpieczeństwa
Warunkowe renderowanie decyduje o tym, co jest wyświetlane w interfejsie użytkownika, ale ukrywanie elementu nie zastępuje prawdziwego bezpieczeństwa. Na przykład:
{isAdmin && <DeleteButton />}
To zapobiega temu, by zwykły użytkownik widział przycisk, ale serwer nadal musi niezależnie potwierdzić, że żądanie pochodzi rzeczywiście od osoby upoważnionej do usuwania danych. Rozważmy to w ten sposób:
Frontend
↓
Controls what users SEE
Backend
↓
Controls what users CAN DO
Nigdy nie traktuj warunkowego renderowania po stronie klienta jako mechanizmu autoryzacji.
3. Interfejs użytkownika oparty na uprawnieniach
Bardziej złożone aplikacje często wymagają większej szczegółowości niż proste role takie jak:
Admin
User
Zamiast tego można zdefiniować konkretne uprawnienia, na przykład:
CAN_VIEW_USERS
CAN_EDIT_USERS
CAN_DELETE_USERS
CAN_EXPORT_REPORT
Komponenty mogą wtedy renderować każdą akcję osobno, w zależności od dostępnych uprawnień:
function UserActions({ permissions }) {
return (
<>
{permissions.includes("CAN_EDIT_USERS") && (
<button>Edit</button>
)}
{permissions.includes("CAN_DELETE_USERS") && (
<button>Delete</button>
)}
</>
);
}
Taki podejście daje znacznie dokładniejszą kontrolę nad tym, co może robić każdy użytkownik.
4. Stany ładowania
Załóżmy, że panel sterowania wysyła żądanie do API, które potrzebuje dwóch sekund na obsłużenie. Co powinno się pojawić na ekranie w tym czasie? Z pewnością nie pusta strona — zamiast tego chcemy wskaźnika ładowania:
if (loading) {
return <p>Loading products...</p>;
}
Cały proces wygląda w ten sposób:
API Request
↓
Loading = true
↓
Show Loader
↓
API Response
↓
Loading = false
↓
Show Content
Wskaźniki ładowania sprawiają, że aplikacja wydaje się responsywna nawet przy wolnym połączeniu sieciowym.
5. Ładowacze szkieletowe
Zamiast zwykłej wiadomości tekstowej, takiej jak:
Loading...
wiele nowoczesnych interfejsów pokazuje element zastępczy w kształcie treści, która ma zostać załadowana — Ładowacz szkieletowy. Na przykład:
┌──────────────────────┐
│ █████████████ │
│ ███████ │
│ █████████████████ │
└──────────────────────┘
Gdy tylko rzeczywiste dane wrócą, zastępują one element tymczasowy:
┌──────────────────────┐
│ MacBook Air │
│ ₹99,999 │
│ ⭐⭐⭐⭐⭐ │
└──────────────────────┘
Podstawowa logika React pozostaje równie prosta:
return loading
? <ProductSkeleton />
: <ProductCard />;
Jedyna rzeczywista różnica polega na lepszym doświadczeniu użytkownika, jakie to zapewnia.
6. Stany błędów
Wymiany danych z API nie zawsze przebiegają gładko.
Połączenie może zostać przerwane.
Serwer może się zawiesić.
Zapytanie może wygasnąć z powodu czasu oczekiwania.
Zamiast pozwolić, by aplikacja się zawiesiła lub zatrzymała, należy zamiast tego wyświetlić stan błędu.
if (error) {
return (
<div>
<h2>Something went wrong.</h2>
<button>Try Again</button>
</div>
);
}
Umiejętne radzenie sobie z awariami jest cechą wyróżniającą interfejsy o jakości produkcyjnej.
7. Ładowanie + Błąd + Sukces
W praktyce te trzy stany prawie zawsze pojawiają się jednocześnie.
function ProductList({
loading,
error,
products
}) {
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Something went wrong.</p>;
}
return <Products products={products} />;
}
Można sobie to przedstawić w ten sposób:
Request
│
├── Loading → Loader
│
├── Failed → Error
│
└── Success → Data
Gdy przejdziesz do wywołań API oraz funkcji useEffect w późniejszych częściach tego kursu, zobaczysz to dokładnie takie wzorzec wielokrotnie.
8. Stany puste
To, że żądanie zakończyło się sukcesem, nie gwarantuje obecności rzeczywistych danych do wyświetlenia.
Załóżmy, że użytkownik wyszuka coś w stylu:
"React Quantum Pizza Developer"
Samo wywołanie API zakończyło się bez żadnych błędów.
Jednak wynik może wyglądać tak:
products.length === 0
Zamiast pozostawiać ekran pusty, daj użytkownikowi coś znaczącego do zobaczenia.
if (products.length === 0) {
return (
<div>
<h2>No Products Found</h2>
<p>Try changing your search.</p>
</div>
);
}
Stany puste mają duże znaczenie dla dopracowanej środowiska użytkownika.
Ładowanie vs pusty stan vs błąd
Nowicjusze w React często mylą te trzy przypadki, ale reprezentują one zupełnie różne sytuacje.
LOADING
Data hasn't arrived yet.
EMPTY
Data arrived, but nothing exists.
ERROR
Something failed.
9. Wielokrotne warunki
Czasami to, co wyświetlisz, zależy od więcej niż jednego warunku połączonego razem.
Rozważmy ten przepływ:
Is User Logged In?
↓
Is Subscription Active?
↓
Is User Admin?
↓
Show Admin Dashboard
Kuszące jest umieszczenie tego wszystkiego w jednym ogromnym, zagnieżdżonym wyrażeniu ternarnym:
condition1
? condition2
? condition3
? <A />
: <B />
: <C />
: <D />
Kompiluje się bez problemów.
Ale jest koszmarem do czytania.
Lepszym podejściem jest wyraźne rozdzielenie każdego warunku.
if (!isLoggedIn) {
return <Login />;
}
if (!hasSubscription) {
return <UpgradePlan />;
}
if (isAdmin) {
return <AdminDashboard />;
}
return <UserDashboard />;
Ta wersja jest o wiele łatwiejsza do zrozumienia.
10. Klauzule ochronne
Wzorzec, który właśnie zobaczyłeś, ma nazwę: klauzule ochronne, znane również jako wczesne zwracanie.
Zamiast układać warunki jeden wewnątrz drugiego:
if
└── if
└── if
└── UI
zajmij się przypadkami krawędziowymi od razu i zwróć wynik wcześnie.
if (loading) return <Loader />;
if (error) return <ErrorPage />;
if (!user) return <Login />;
return <Dashboard />;
Wynik jest klarowny.
Jest czytelny.
I o wiele łatwiej jest go debugować.
Myśl jak deweloper React
Zanim napiszesz komponent, pomocne jest zadanie sobie pytania:
"Jakie są wszystkie możliwe stany, w których może się znajdować ten ekran?"
Dla strony generowanej na podstawie wywołania API lista ta może wyglądać tak:
Loading
Error
Empty
Success
W przypadku procesu autoryzacji może wyglądać tak:
Logged Out
Checking Authentication
Logged In
Unauthorized
Określenie tych stanów przed rozpoczęciem pisania kodu znacznie ułatwia zrozumienie ostatecznego komponentu.
Częste błędy początkujących
Nagromadzanie nawarstwionych struktur warunkowych
Nie rezygnuj z czytelności tylko po to, by zaoszczędzić kilka wierszy kodu.
Pomijanie stanu pustego
API zwracające pusty tablicę to nie to samo co błąd — traktuj to jako odrębny przypadek.
Mieszanie ukrywania interfejsu z rzeczywistą bezpieczeństwem
Ukrywanie czegoś takiego jak:
<DeleteButton />
nic nie zapobiega temu, by ktoś bezpośrednio skierował się do Twojego punktu końcowego API.
Rzeczywista autoryzacja musi znajdować się po stronie serwerowej.
Pozwolenie && na wyświetlanie niewłaściwej treści
Uważaj na kod w stylu:
{items.length && <ProductList />}
Jeśli items.length wynosi 0, React może wyświetlić:
0
bezpośrednio na stronie.
Bardziej bezpieczna wersja to:
{items.length > 0 && <ProductList />}
Teraz warunek jest oceniany jako prawdziwy lub fałszywy.
Najlepsze praktyki
Czytelność powinna zawsze być najważniejsza przy warunkowym renderowaniu.
Najlepiej stosować wzory takie jak:
if (loading) return <Loader />;
zamiast gromadzić warunki w głęboko zagnieżdżonym JSX.
Dla stanów, które renderujesz często, przenieś je do osobnych, wielokrotnie używalnych komponentów:
<Loader />
<ErrorMessage />
<EmptyState />
Gdy komponenty stają się coraz bardziej złożone, oddziel logikę biznesową od tego, co faktycznie jest wyświetlane.
I nie projektuj tylko dla „szczęśliwej ścieżki” — planuj na każdy stan, w którym interfejs może się rzeczywiście znajdować.
Miły projekt: Inteligentna karta sterująca
Jako ćwiczenie spróbuj stworzyć kartę sterującą, która uwzględnia takie przypadki jak:
User Not Logged In
↓
Login ScreenUser
Logged In
↓
Loading Dashboard
↓
┌──────┴──────┐
Error Success
↓ ↓
Error UI Data Exists?
↙ ↘
YES NO
↓ ↓
Dashboard Empty State
Następnie dodaj powyżej zachowanie zależne od roli użytkownika:
Admin
↓
Edit + Delete
User
↓
View Only
Taki projekt łączy jednocześnie kilka koncepcji:
- Props
- Stan aplikacji
- Zdarzenia
- Renderyzacja warunkowa
Dokładnie w ten sposób poszczególne elementy React zaczynają współpracować ze sobą, gdy tworzysz coś rzeczywistego.
Pytania z rozmowy kwalifikacyjnej
Czym jest renderyzacja warunkowa?
To praktyka pokazywania różnej interfejsu użytkownika w zależności od aktualnego stanu lub warunków w aplikacji.
Jaka jest różnica między && a wyrażeniem ternarnym?
Użyj &&, gdy chcesz, aby coś zostało wyświetlone tylko w przypadku prawdziwym, a w pozostałych sytuacjach nic nie pojawiło się na ekranie. Użyj struktury ternarnej, gdy potrzebujesz różnych wyników dla przypadków prawdziwego i fałszywego.
Czym jest stan pusty?
To interfejs, który pokazujesz, gdy żądanie zakończyło się pomyślnie, ale po prostu nie ma danych do wyświetlenia.
Czy renderowanie oparte na rolach w frontendzie wystarcza do zapewnienia bezpieczeństwa?
Nie. To wygoda interfejsu — rzeczywiste sprawdzenia uprawnień muszą nadal odbywać się w backendzie.
Jaka jest korzyść z wcześniejszych powrotów?
Zmniejszają one poziom nawiasowania, co ułatwia czytanie i utrzymanie komponentów warunkowych.
Główne wnioski
Do tej pory omówiliście cały zakres renderowania warunkowego w React.
Zobaczyliście, jak aplikacje w świecie rzeczywistym radzą sobie z widokami dla zalogowanych i niezalogowanych użytkowników, ograniczając dostęp do ekranów według ról, kontrolując funkcje za pomocą szczegółowych uprawnień, pokazując elementy oznaczające ładowanie danych, używając placeholderów w formie szkieletu zamiast zwykłego tekstu, wyświetlając błędy w przypadku nieudanych żądań, informując użytkowników, gdy zbiór wyników jest pusty, łącząc kilka warunków w jeden spójny proces, upraszczając zagnieżdżone sprawdzania za pomocą klauzul ochronnych oraz stosując ogólne praktyki, które zapewniają utrzymaność tej logiki w rzeczywistym kodzie.
Renderyzacja warunkowa pozwala aplikacji pokazać odpowiednie doświadczenie dla danej sytuacji.
Jednak nadal stoi przed nami wyzwanie.
Załóżmy, że API zwraca:
1,000 Products
Czy naprawdę napisalibyście:
<Product />
<Product />
<Product />
...
tysiąc razy osobno?
Oczywiście, że nie.
React oferuje o wiele lepszy sposób na radzenie sobie z tym problemem.
W Części 9A dowiesz się, jak renderować listy za pomocą map(), a także zobaczysz, jak pojedynczy komponent może wygenerować setki lub tysiące elementów interfejsu bezpośrednio na podstawie Twoich danych.
Niedługo potem przyjdzie czas na jedno z najbardziej klasycznych pytań z rozmów rekrutacyjnych dotyczących Reacta:
Dlaczego React wymaga
key?
Do zobaczenia w Części 9A — Renderowanie list i kluczy w React.
Literatura pokrewna
- React 19.2 wyjaśniony: Activity, useEffectEvent i statyczne renderowanie — Dowiedz się, jak nowy komponent Activity, hook useEffectEvent oraz częściowe statyczne renderowanie w React 19.2 eliminują ukryte koszty wydajności w nowoczesnych interfejsach użytkownika.