Menu

Pytania rekrutacyjne z React i odpowiedzi (React 19)

Około 30 pytań rekrutacyjnych z React, od poziomu początkującego do zaawansowanego, każde z krótką odpowiedzią, którą zaakceptuje rekruter, i z żywym kodem tam, gdzie łatwiej coś zobaczyć, niż o tym przeczytać.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

To pytania rekrutacyjne z React, które padają najczęściej, pogrupowane według poziomu i odpowiedziane tak, jak oczekuje rekruter: krótko, poprawnie dla React 19 i z uzasadnieniem zasady. Tam, gdzie zobaczenie zachowania jest lepsze niż czytanie o nim, odpowiedź ma żywy edytor, a kilka pytań prosi cię, żebyś przewidział wynik w konsoli, zanim uruchomisz kod.

Zanim klikniesz: co wypisze konsola pod podglądem i co pokaże przycisk? Odpowiedź znajdziesz w sekcji dla początkujących poniżej.

Pytania dla początkujących

Co to jest React?

React to biblioteka JavaScript do budowania interfejsów użytkownika z komponentów. Piszesz funkcje, które zwracają opis UI dla bieżących danych, a gdy te dane się zmieniają, React ustala, co się zmieniło, i aktualizuje za ciebie DOM. Obsługuje tylko warstwę widoku: routing, pobieranie danych i formularze pochodzą z innych bibliotek albo z frameworka takiego jak Next.js.

Co to jest JSX?

JSX to rozszerzenie składni, które pozwala pisać znaczniki wewnątrz JavaScriptu. Kompilator (Babel, esbuild, SWC) zamienia każdy tag w wywołanie, które tworzy element Reacta, więc <p className="note">{text}</p> staje się obiektem z type: 'p' i propsami. Ponieważ to JavaScript, używasz className zamiast class, wyrażenia umieszczasz w {}, a każdy tag musi być zamknięty. Strona o JSX szczegółowo omawia zasady.

Czym różni się komponent od elementu?

Komponent to funkcja (albo klasa), która przyjmuje propsy i zwraca UI. Element to zwykły obiekt tworzony przez JSX, opisujący, co wyrenderować: <Avatar size={40} /> to element, którego type to funkcja Avatar. React wywołuje twój komponent, żeby zamienić elementy w kolejne elementy, aż do tagów DOM. Elementy są tanie i niemutowalne; nigdy sam nie wywołujesz komponentu.

Czym różnią się propsy od stanu?

Propsy przekazuje rodzic i wewnątrz komponentu są tylko do odczytu. Stan należy do komponentu, zmienia się w czasie przez setter, a każda zmiana planuje nowe renderowanie. Często mówi się tak: propsy konfigurują komponent, a stan to jego pamięć. Ta sama wartość może być stanem w rodzicu i propsem w jego dziecku.

Co wypisuje pierwszy przykład na tej stronie?

Wypisuje logged: 0, a przycisk pokazuje Count: 1, a nie 2. Stan to migawka: count jest stałą przez całe renderowanie, więc oba wywołania setCount(count + 1) liczą 0 + 1, a log czyta starą wartość, bo nowa istnieje dopiero w następnym renderowaniu. Żeby dodać dwa, użyj formy z funkcją aktualizującą: dwa razy setCount((c) => c + 1).

Dlaczego listy potrzebują kluczy i dlaczego indeks to zły klucz?

React używa key, żeby dopasować każdy element listy do elementu z poprzedniego renderowania. Jeśli używasz indeksu tablicy i wstawisz element na górę, każdy indeks się przesuwa, więc React zostawia stan każdego wiersza na jego starej pozycji i przypina go do złych danych.

Zaznacz Adę, a potem kliknij "Add to top": zaznaczenie przeskakuje na nowy pierwszy wiersz. Zmień key={index} na key={person.id} i spróbuj ponownie, a zaznaczenie zostanie przy Adzie. W trybie deweloperskim React ostrzega też, gdy brakuje kluczy; podgląd działa jak build produkcyjny, więc tutaj tego ostrzeżenia nie zobaczysz.

Co to jest kontrolowane pole?

Pole, którego wartość pochodzi ze stanu Reacta: przekazujesz value i aktualizujesz stan w onChange, więc React jest jedynym źródłem prawdy i możesz walidować albo przekształcać każde naciśnięcie klawisza. Pole niekontrolowane trzyma własną wartość w DOM, a ty odczytujesz ją, gdy jest potrzebna, przez ref albo FormData formularza. Pola kontrolowane to domyślny wybór, gdy UI reaguje na to, co wpisuje użytkownik.

Co oznacza podnoszenie stanu?

Gdy dwa komponenty potrzebują tych samych danych, przenosisz stan do ich najbliższego wspólnego rodzica i przekazujesz go w dół jako propsy, razem z funkcją do jego zmiany. Dzieci przestają mieć własne kopie, więc nigdy nie mogą się ze sobą nie zgadzać. To standardowa odpowiedź na pytanie „jak komponenty rodzeństwa dzielą stan", zanim sięgniesz po kontekst albo magazyn danych.

Jakie są zasady hooków?

Wywołuj hooki tylko na najwyższym poziomie komponentu albo własnego hooka, nigdy w warunkach, pętlach ani zagnieżdżonych funkcjach, i tylko z funkcji Reacta, a nie ze zwykłych funkcji JavaScript. React identyfikuje każdy hook po kolejności wywołań, więc ta kolejność musi być identyczna przy każdym renderowaniu. Pakiet eslint-plugin-react-hooks pilnuje obu zasad.

Co to jest fragment?

Fragment grupuje kilka elementów bez dodawania węzła DOM: <>...</> albo <Fragment key={id}>, gdy potrzebujesz klucza w liście. Komponent musi zwrócić jeden root, a fragmenty pozwalają, żeby tym rootem było kilka elementów rodzeństwa, co ma znaczenie w tabelach i układach flex, gdzie dodatkowy div zepsułby znaczniki.

Pytania dla średniozaawansowanych

Jak działa tablica zależności useEffect?

React uruchamia efekt po zatwierdzeniu renderowania (zwykle po odmalowaniu ekranu przez przeglądarkę), a potem ponownie po każdym późniejszym renderowaniu, w którym zmieniła się wartość w tablicy (porównywana przez Object.is). Bez tablicy uruchamia się po każdym renderowaniu; z [] raz po pierwszym. Każda wartość z komponentu, którą czyta efekt, należy do tablicy, a linter mówi ci, gdy którejś brakuje.

Kiedy uruchamia się funkcja czyszcząca efektu?

Funkcja, którą zwracasz z efektu, uruchamia się przed ponownym uruchomieniem efektu z nowymi zależnościami i raz, gdy komponent się odmontowuje. To tam cofasz to, co efekt ustawił: czyścisz timer, usuwasz nasłuchiwanie, zamykasz połączenie.

Kliknij "Switch room" i obserwuj konsolę: najpierw uruchamia się funkcja czyszcząca starego pokoju, a potem efekt łączy się z nowym. "Hide chat" uruchamia tylko funkcję czyszczącą. W trybie deweloperskim StrictMode dodaje przy montowaniu jedno dodatkowe połączenie, rozłączenie i połączenie, żeby sprawdzić, czy twoja funkcja czyszcząca działa; podgląd działa jak build produkcyjny, więc widzisz tylko prawdziwą sekwencję.

Dlaczego mój stan wygląda na nieaktualny w setInterval?

Callback interwału to domknięcie nad count z renderowania, które go utworzyło. Z pustą tablicą zależności efekt nigdy nie uruchamia się ponownie, więc callback w nieskończoność czyta to pierwsze count.

broken dochodzi do 1 i się zatrzymuje, bo każde tyknięcie liczy 0 + 1. fixed liczy dalej, bo funkcja aktualizująca dostaje najnowszą wartość. Dodanie broken do tablicy zależności też działa, kosztem czyszczenia i tworzenia interwału od nowa co sekundę.

Co to jest batching?

React grupuje kilka aktualizacji stanu w jedno renderowanie. Od React 18 dotyczy to wszystkich miejsc: handlerów zdarzeń, timeoutów, obietnic i natywnych nasłuchiwań zdarzeń. Jeśli potrzebujesz, żeby DOM zaktualizował się od razu po jednej aktualizacji, flushSync z react-dom pozwala z tego zrezygnować, ale rzadko jest potrzebny.

Kliknięcie wypisuje jedno render 1 1 1, a nie trzy renderowania. Przed React 18 trzy aktualizacje stanu w timeoucie oznaczały trzy renderowania.

Co to wypisze? (kolejność efektów)

Przeczytaj kod i zapisz cztery linie, które wypisze konsola, w kolejności, zanim na nią spojrzysz.

Renderowanie idzie od góry w dół: render App, potem render Child. Efekty uruchamiają się od dołu w górę, po zatwierdzeniu całego drzewa: effect Child, potem effect App. Efekt rodzica może polegać na tym, że efekty jego dzieci już się wykonały.

Czym różni się useMemo od useCallback?

useMemo(() => compute(a, b), [a, b]) zapamiętuje wynik obliczenia. useCallback(fn, [deps]) zapamiętuje samą funkcję, więc to to samo co useMemo(() => fn, [deps]). Oba pomagają tylko wtedy, gdy coś dalej porównuje po referencji: dziecko z memo albo tablica zależności innego hooka. Od React Compiler 1.0 (październik 2025) krok budowania może dodawać tę memoizację automatycznie, ale ręczna memoizacja nadal działa i nadal zobaczysz ją w większości projektów.

Co robi React.memo i dlaczego często potrzebuje useCallback?

memo(Component) pomija ponowne renderowanie komponentu, gdy jego propsy są takie same jak ostatnio, porównywane po kolei przez Object.is. Funkcja napisana w linii w rodzicu jest nową funkcją przy każdym renderowaniu, więc liczy się jako zmieniony prop i unieważnia memo. useCallback utrzymuje tę samą funkcję między renderowaniami.

Wyczyść konsolę i kliknij "Re-render parent": pojawia się tylko render inline handler. Drugie dziecko dostało tę samą funkcję i zostało pominięte. Usuń memo(...) wokół Button, a oba będą wypisywać log przy każdym kliknięciu, co pokazuje, że samo useCallback nic nie daje.

Co to jest kontekst i ile kosztuje?

Kontekst przekazuje wartość każdemu komponentowi pod providerem bez przeprowadzania jej przez propsy: createContext, potem <ThemeContext value={theme}> w React 19 (albo <ThemeContext.Provider>) i useContext(ThemeContext) do odczytu. Koszt polega na tym, że każdy komponent, który czyta kontekst, renderuje się ponownie, gdy zmienia się wartość, a memo tego nie zatrzymuje. Utrzymuj go tanim, dzieląc niezwiązane wartości na osobne konteksty, owijając wartość będącą obiektem w useMemo i trzymając szybko zmieniający się stan poza kontekstem całej aplikacji. Zobacz useContext.

Co to są refy i kiedy używać ich zamiast stanu?

useRef(initial) zwraca obiekt, którego właściwość current przetrwa między renderowaniami, a jej zmiana nie wywołuje renderowania. Używaj refa do wartości, od których nie zależy ekran (ID timera, poprzednia wartość, flaga) i do dostępu do DOM (<input ref={inputRef}>, a potem inputRef.current.focus()). Jeśli wartość jest pokazywana na ekranie, należy do stanu.

Co to jest wirtualny DOM i co to jest rekoncyliacja?

Przy każdym renderowaniu React buduje nowe drzewo elementów i porównuje je z poprzednim; to porównanie to rekoncyliacja. Jeśli typ elementu się zmienił (div stał się section albo ProfileA stał się ProfileB), React niszczy to poddrzewo razem z jego stanem i buduje nowe. Jeśli typ jest ten sam, zachowuje węzeł DOM i aktualizuje zmienione propsy, a do dopasowania dzieci w listach używa kluczy. „Wirtualny DOM" to popularna nazwa tego drzewa w pamięci; szybkość bierze się z dotykania tylko tego, co się zmieniło, a nie z tego, że samo drzewo jest szybkie.

Co to jest własny hook?

Funkcja, której nazwa zaczyna się od use i która wywołuje inne hooki, dzięki czemu możesz ponownie używać logiki ze stanem w różnych komponentach: useOnlineStatus, useLocalStorage, useDebouncedValue. Każdy komponent, który ją wywołuje, dostaje własny, niezależny stan; własny hook współdzieli logikę, a nie dane. Zobacz własne hooki.

Co robi StrictMode?

<StrictMode> włącza dodatkowe sprawdzenia tylko w trybie deweloperskim. Renderuje komponenty dwa razy, żeby ujawnić nieczyste renderowanie, przy montowaniu raz uruchamia efekty w kolejności setup, cleanup, setup, żeby ujawnić brakujące funkcje czyszczące, robi to samo z ref callbackami i ostrzega przed przestarzałymi API. Nic z tego nie dzieje się w buildzie produkcyjnym, więc podwójny log w konsoli w trybie deweloperskim jest oczekiwany i nie jest błędem.

Pytania zaawansowane

Co to wypisze? (render, efekt układu, efekt)

Jeszcze jedno przewidywanie. Trzy logi, trzy różne momenty renderowania.

Kolejność to: renderowanie, potem useLayoutEffect, potem useEffect, choć useEffect jest napisany jako pierwszy. Efekty układu uruchamiają się po aktualizacji DOM przez React, ale przed odmalowaniem ekranu przez przeglądarkę, dlatego nadają się do mierzenia elementu; zwykłe efekty uruchamiają się później, zwykle po odmalowaniu, i powinny być wyborem domyślnym.

Co to jest granica błędów?

Komponent klasowy, który łapie błędy rzucone podczas renderowania jego dzieci i pokazuje widok zastępczy, zamiast odmontowywać całą aplikację. Potrzebuje static getDerivedStateFromError (do przełączenia na widok zastępczy) i zwykle componentDidCatch (do logowania). Wciąż nie ma odpowiednika w postaci hooka, więc w projektach z komponentami funkcyjnymi zespoły piszą jedną klasę albo używają pakietu react-error-boundary.

Kliknij "Break it": widok zastępczy zastępuje tylko dzieci granicy, przycisk zostaje, a React nadal zgłasza w konsoli złapany błąd. Granice nie łapią błędów w handlerach zdarzeń, w kodzie asynchronicznym takim jak setTimeout ani w samej granicy. W takich przypadkach używasz try/catch i ustawiasz stan.

Co to jest portal?

createPortal(children, domNode) z react-dom renderuje dzieci do innego węzła DOM, zwykle document.body, a w drzewie Reacta zostawia je w tym samym miejscu. Modale, tooltipy i powiadomienia używają go, żeby wydostać się spod overflow: hidden albo kontekstu stosu rodzica. Kontekst nadal działa przez portal, a zdarzenia propagują się przez drzewo Reacta, więc kliknięcie wewnątrz portalu dociera do onClick rodzica, choć węzły DOM są gdzie indziej. Zobacz portale.

Co to są Suspense i lazy?

lazy(() => import('./Chart')) ładuje kod komponentu przy jego pierwszym renderowaniu, co dzieli bundle. <Suspense fallback={<Spinner />}> pokazuje widok zastępczy, dopóki cokolwiek w jego środku wciąż się ładuje: leniwy kod albo dane odczytywane przez use(promise) lub framework obsługujący Suspense. Rozmieszczenie granic decyduje, które części strony czekają razem.

import { lazy, Suspense } from 'react';

const Chart = lazy(() => import('./Chart.jsx'));

export default function Dashboard() {
    return (
        <Suspense fallback={<p>Loading chart...</p>}>
            <Chart />
        </Suspense>
    );
}

Co robi useTransition?

Oznacza aktualizację stanu jako niepilną. const [isPending, startTransition] = useTransition() i startTransition(() => setTab('posts')) pozwalają Reactowi utrzymać responsywność bieżącego ekranu i przerwać wolne renderowanie, jeśli przyjdzie pilniejsza aktualizacja, na przykład naciśnięcie klawisza. isPending mówi ci, kiedy pokazać wskazówkę. W React 19 przekazywana funkcja może być async i to właśnie React nazywa akcją. Nie używaj go do aktualizacji własnej wartości pola tekstowego; ta aktualizacja musi być pilna.

Co to są React Server Components?

Komponenty, które wykonują się tylko na serwerze (albo w czasie budowania) i wysyłają do przeglądarki swój wyrenderowany wynik, nigdy swój kod. Mogą być async i bezpośrednio czytać bazę danych albo system plików, ale nie mogą używać stanu, efektów ani API przeglądarki. Plik oznaczony 'use client' zaczyna granicę klienta, w której żyją komponenty interaktywne, a 'use server' oznacza funkcje serwerowe, które może wywołać kod kliencki. Używasz ich przez framework taki jak Next.js z App Routerem; zwykłe aplikacje Vite nie mają serwera, który by je uruchamiał.

Co zmieniło się w React 19?

  • Akcje: funkcje async w przejściach, ze stanem oczekiwania obsługiwanym za ciebie. <form action={fn}> wywołuje fn z FormData formularza.
  • Nowe hooki: useActionState do wyniku i stanu oczekiwania formularza, useOptimistic do optymistycznego UI i useFormStatus w react-dom.
  • use: odczytuje obietnicę (zawieszając komponent, dopóki się nie rozwiąże) albo kontekst i w odróżnieniu od innych hooków można go wywołać w warunku.
  • ref jako prop: komponenty funkcyjne dostają ref jak każdy inny prop, więc nowy kod nie potrzebuje już forwardRef. Ref callbacki mogą zwracać funkcję czyszczącą.
  • <Context> jako provider oraz tagi <title> i <meta>, które przenoszą się do head dokumentu.
  • Usunięcia: ReactDOM.render, string refs, przestarzały kontekst i defaultProps w komponentach funkcyjnych (używaj domyślnych parametrów).
// React 19: ref is a normal prop
function TextInput({ ref, ...props }) {
    return <input ref={ref} {...props} />;
}

Strona o React 19 ma działające przykłady każdej z tych zmian.

Jak testować komponenty Reacta?

Przez React Testing Library i runner taki jak Vitest albo Jest. Renderujesz komponent, znajdujesz elementy tak, jak zrobiłby to użytkownik (po roli, etykiecie albo tekście), wchodzisz w interakcję przez user-event i sprawdzasz to, co widać, a nie wewnętrzny stan. Dzięki temu testy pozostają aktualne przy refaktoryzacji.

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Counter from './Counter';

test('counts clicks', async () => {
    const user = userEvent.setup();
    render(<Counter />);
    await user.click(screen.getByRole('button', { name: /clicked 0 times/i }));
    expect(screen.getByRole('button')).toHaveTextContent('Clicked 1 times');
});

toHaveTextContent pochodzi z @testing-library/jest-dom. Do całych przepływów między stronami zespoły dodają na wierzchu Playwright albo Cypress.

Jak znaleźć i naprawić problem z wydajnością?

Najpierw mierz. Otwórz kartę Profiler w React DevTools, nagraj wolną interakcję i przeczytaj wykres płomieniowy (flame chart): pokazuje, które komponenty się wyrenderowały, ile każdy trwał i (z ustawieniem "record why each component rendered") dlaczego. Typowe poprawki, w kolejności, w jakiej warto je próbować: przenieś stan w dół, żeby zależało od niego mniej komponentów, przekazuj wolne poddrzewa jako children, zmemoizuj kosztowną część przez memo, useMemo i useCallback, wirtualizuj długie listy i odraczaj ciężkie aktualizacje przez useTransition albo useDeferredValue. Profiluj w buildzie produkcyjnym albo profilującym; buildy deweloperskie są wolniejsze i wyolbrzymiają czasy renderowania.

Dlaczego komponent powinien być czysty?

Przy tych samych propsach, stanie i kontekście komponent powinien zwracać ten sam JSX i podczas renderowania nie zmieniać niczego poza sobą. React może wyrenderować komponent więcej niż raz, nie po kolei albo wyrzucić renderowanie (przejścia, Suspense, StrictMode), więc efekty uboczne w ciele, takie jak mutowanie zmiennej albo wysyłanie żądania, wykonują się nieprzewidywalną liczbę razy. Efekty uboczne należą przede wszystkim do handlerów zdarzeń, a do efektów wtedy, gdy nie spowodowało ich żadne zdarzenie.

Kiedy nie używać efektu?

Gdy wartość można wyliczyć podczas renderowania (przefiltrowane listy, pełne imiona, sumy), gdy praca wynika z akcji użytkownika (umieść ją w handlerze zdarzenia) i gdy chcesz zresetować stan przy zmianie propsa (użyj key). Efekty służą do synchronizacji z czymś spoza Reacta: połączeniem sieciowym, API przeglądarki, zewnętrznym widżetem. Łańcuchy efektów, które ustawiają stan, to częste źródło dodatkowych renderowań i błędów.

Najczęściej zadawane pytania

Co to jest React?

React to biblioteka JavaScript do budowania interfejsów użytkownika z komponentów. Opisujesz, jak ma wyglądać ekran dla bieżących danych, a React aktualizuje prawdziwy DOM, gdy te dane się zmieniają.

Czym różni się stan od propsów?

Propsy to dane wejściowe, które rodzic przekazuje komponentowi, a komponent traktuje je jako tylko do odczytu. Stan to dane, których właścicielem jest komponent i które zmieniają się w czasie przez setter, co sprawia, że React renderuje go ponownie.

Co to są hooki w React?

Hooki to funkcje, których nazwy zaczynają się od use i które pozwalają komponentowi funkcyjnemu korzystać z funkcji Reacta: useState do stanu, useEffect do efektów ubocznych, useContext do kontekstu, useRef do wartości, które nie wywołują renderowania, i innych.

Co to jest wirtualny DOM?

To nazwa opisu UI, który React trzyma w pamięci. Przy każdym renderowaniu React buduje nowy opis, porównuje go z poprzednim i stosuje w prawdziwym DOM tylko różnice. To porównanie nazywa się rekoncyliacją.

Dlaczego klucze mają znaczenie w listach w React?

Klucz mówi Reactowi, który element listy jest którym między renderowaniami. Ze stabilnymi kluczami (na przykład id) React utrzymuje stan i węzeł DOM każdego elementu przy właściwych danych, gdy elementy są dodawane, usuwane albo zmieniają kolejność. Użycie indeksu tablicy psuje to, gdy zmienia się kolejność.

Co to jest JSX?

JSX to rozszerzenie składni, które pozwala pisać w JavaScripcie znaczniki podobne do HTML. Kompilator zamienia każdy tag w wywołanie funkcji, które tworzy element Reacta, więc <h1>Hi</h1> staje się zwykłym obiektem opisującym h1.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ