Menu Zamknij
2 września 2024

Zmiany i nowe feature-y w React 19

Spis treści

Podziel się wpisem ze znajomymi!

React 19. Zmiany i nowe feature-y.

Wstęp

Jeśli czytasz ten artykuł, to oznacza, że React 19 jest albo bardzo bliski wydania, albo już ujrzał światło dzienne. Dlatego przejrzyjmy nowe funkcjonalności zaproponowane przez twórców w najnowszym standardzie.

Poznajmy, jakie nowe smaczki zostały wprowadzone oraz w jaki sposób ulepszać jakość swojego kodu, wykorzystując React 19.

Feature 1. Nowy hook.

useTransition() - nowy hook pozwalający ustawiać priorytet renderowania.

Przeanalizuj poniższy przykład, który został napisany nieoptymalnie.

function App() {
  const [tab, setTab] = useState("products");

  function switchTab(tab) {
    setTab(tab);
  }

  return (
    <>
      <button onClick={() => switchTab("home")}>Home</button>
      <button onClick={() => switchTab("products")}>products</button>
      <button onClick={() => switchTab("about")}>about</button>
      {tab === "products" ? <Products /> : null}
      {tab === "home" ? <Home /> : null}
      {tab === "about" ? <About /> : null}
    </>
  );
}

Wyobraźmy sobie, że komponent Products jest bardzo długi i ma wiele podkomponentów. Skutkować to będzie tym, że gdy chcemy zmienić zakładkę (tab), lista produktów renderuje się i jest dodana do kolejki. Musimy czekać, aż wyrenderuje się ona całkowicie i dopiero wtedy możemy przejść na inną zakładkę, zamiast móc przełączać się między nimi bardziej dynamicznie.

Rozwiązaniem na ten problem jest właśnie hook useTransition, który zmienia stan kolejki i przerywa status renderowania, więc możemy przełączyć się od razu bez czekania na <Products />.

Tak wygląda ulepszony komponent App:

function App() {
  const [tab, setTab] = useState("products");
  const [isPending, startTransition] = useTransition(); // hook zwraca stan isPending operacji i funkcję włączającą transition

  function switchTab(tab) {
    startTransition(() => {
      // rozpoczynamy transition
      setTab(tab);
    });
  }

  return (
    <>
      <button onClick={() => switchTab("home")}>Home</button>
      <button onClick={() => switchTab("products")}>products</button>
      <button onClick={() => switchTab("about")}>about</button>
      {/* dla lepszego UI */}
      {isPending ? <p>Loading...</p> : null}
      {!isPending && tab === "products" ? <Products /> : null}
      {!isPending && tab === "home" ? <Home /> : null}
      {!isPending && tab === "about" ? <About /> : null}
    </>
  );
}

Feature 2. Nowy kompilator.

Nowy kompilator do React wyrósł z projektu React Forget. Dzięki kompilatorowi nie potrzebujemy już:

- React.memo(),
- React.useMemo(),
- React.useCallback(),

Kompilator jest projektem open source i bardzo dynamicznie się rozwija. Dzięki jego dodaniu do React, developerzy nie będą musieli się już więcej martwić o performance naszej aplikacji. Memoizacja i optymalizacja zostaje dodana automatycznie do naszego kodu “pod spodem”.

Jest to też dobry kierunek rozwoju. Wśród najbardziej popularnych bibliotek frontendowych (React, Vue i Angular) obecnie React ma najsłabszy performance. Dzięki wprowadzeniu kompilatora, jest duża szansa na wyrównanie różnic pomiędzy performancem bibliotek.

Feature 3. Actions.

Kiedy chcemy zmodyfikować dane w bazie z poziomu frontendu, musimy zarządzać dwoma kluczowymi stanami:

1. Oczekiwanie na zakończenie operacji (ładowanie),

2. Aktualizacja interfejsu użytkownika po pomyślnym wprowadzeniu zmian.

Akcje w React 19 to funkcje, które automatyzują obsługę stanów takich jak oczekiwanie (pending state), optymistyczne aktualizacje, obsługa błędów oraz interakcje z formularzami. Są one zbliżone do asynchronicznych przejść (transitions, patrz: hook useTransition) i ułatwiają pracę z formularzami oraz asynchronicznymi operacjami. Zasadniczą różnicą jednak jest fakt, że akcje są przeznaczone do formularzy, a transitions głównie do zmian na UI. Mogą być zatem skuteczną alternatywą dla, np. hooka useState.

Akcję możemy przekazywać do formularza funkcji - do property action - co ułatwi obsługę formularzy.

<form action={myAction}>...</form>

Zmienimy tradycyjny komponent formularza:

function App() {
  const [input, setInput] = useState("");
  const [name, setName] = useState(
    () => JSON.parse(localStorage.getItem("name")) || "Anon"
  );

  function handleChange(event) {
    setInput(event.target.value);
  }

  async function handleSubmit(event) {
    event.preventDefault();
    try {
      const newName = await updateNameInDb(input);
      setName(newName);
      setInput("");
    } catch (error) {
      console.error(error.message);
    }
  }

  return (
    <>
      <p>User: {name}</p>
      <form onSubmit={handleSubmit}>
        <input type="text" value={input} onChange={handleChange} required />
        <button type="submit">Send</button>
      </form>
    </>
  );
}

na:

function App() {
  // nie musimy obserwować stanu inputa bo mamy bezpośredni dostęp do obiektu FormData
  // const [input,setInput]=useState("")
  const [name, setName] = useState(
    () => JSON.parse(localStorage.getItem("name")) || "Anon"
  );

  // funkcja handleChange staje się zbędna
  // function handleChange(event){
  //     setInput(event.target.value)
  // }

  // formData object
  async function handleSubmit(formData) {
    // event.preventDefault()
    try {
      // zmieniamy sposób przekazania danych
      const newName = await updateNameInDb(formData.get("name"));
      setName(newName);
    } catch (error) {
      console.error(error.message);
    }
  }

  return (
    <>
      <p>User: {name}</p>
      <form action={handleSubmit}>
        <input
          type="text"
          // value i onChange nie są potrzebne bo nie trackujemy już tych wartości tylko odczytamy je z formData
          // value={input}
          // onChange={handleChange}
          // dodajemy name, żeby móc je odczytać za pomocą formData.get("name")
          name="name"
          required
        />
        <button type="submit">Send</button>
      </form>
    </>
  );
}

W nowym podejściu eliminujemy potrzebę monitorowania stanu formularza przy użyciu hooka useState. Zamiast tego, korzystamy bezpośrednio z FormData, co upraszcza kod i redukuje ilość ręcznej obsługi zdarzeń, takich jak zmiany w polach formularza.

Miej na uwadze również to, że w powyższym przykładzie pokryliśmy jedynie happy path. Nie mamy obsługi errorów i loading state.

Zajmijmy się tym zatem (kod został wyczyszczony z poprzednich komentarzy). Tradycyjne podejście sugeruje rozwiązanie jak poniżej:

function App() {
  const [name, setName] = useState(
    () => JSON.parse(localStorage.getItem("name")) || "Anon"
  );

  // dodajemy stan
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  async function handleSubmit(formData) {
    // ustawiamy stan ładowania
    setIsLoading(true);
    // czyścimy poprzednie błędy
    setError(null);
    try {
      const newName = await updateNameInDb(formData.get("name"));
      setName(newName);
    } catch (error) {
      console.error(error.message);
      // obsługa błędu
      setError(error);
    } finally {
      // zakończenie ładowania
      setIsLoading(false);
    }
  }

  return (
    <>
      <p>User: {name}</p>
      <form action={handleSubmit}>
        <input type="text" name="name" required />
        <button type="submit">Send</button>
      </form>
    </>
  );
}

no i coś nam nie działa 🙂 Konkretnie stan isLoading i isError nie wywołuje reakcji na UI, ponieważ ma niski priorytet aktualizacji. Zastanówmy się, jak to zmienić.

Uwaga: W React 19, akcje są automatycznie traktowane jako część mechanizmu transition, co oznacza, że mają niski priorytet aktualizacji stanu. Z tego powodu bezpośrednia zmiana stanu, jak np. w useState, wewnątrz takiej akcji może nie działać jak oczekiwano. Zamiast tego musimy wykorzystać nowy hook useActionState, który pozwala na bezpieczną aktualizację stanu w kontekście transition.

Hook useActionState obserwuje stan komponentu, pending status i wrapuje akcje w taki sposób, żeby można było używać jej na formularzach.

import { useActionState } from "react";

function App() {
  // state - stan jaki trackujemy (użyjemy name i likwidujemy stan w useState)
  // actionFunction - funkcja akcyjna - zawsze ma dostęp do poprzedniego stanu
  // isPending - stan tranzycji
  // fn - inicjalizacja funkcji akcji
  // initialState - stan początkowy
  // const [state,actionFunction, isPending]=useActionState(fn, initialState)

  const [name, actionFunction, isPending] = useActionState(
    updateName,
    JSON.parse(localStorage.getItem("name")) || "Anon"
  );

  async function updateName(prevState, formData) {
    try {
      const newName = await updateNameInDb(formData.get("name"));
      // nowy stan ustawiamy za pomocą return z akcji
      return newName;
    } catch (error) {
      console.error(error.message);
    }
  }

  if (isPending) {
    return <p>Loading...</p>;
  }
  return (
    <>
      <p>User: {name}</p>
      <form action={actionFunction}>
        <input type="text" name="name" required />
        <button type="submit">Send</button>
      </form>
    </>
  );
}

UWAGA: hook useActionState był nazywany jako useFormState w poprzednich canary releases, a obecnie useFormState jest oznaczony jako deprecated. W literaturze nadal możemy spotkać gdzieniegdzie nazwę useFormState.

Dla wartości prymitywnych, tak jak wyżej, wydaje się to być dość proste. Zobaczmy przykład z obiektem:

import { useActionState } from "react";

function App() {
  const [name, actionFunction, isPending] = useActionState(
    updateName,
    { name: "Anon" } // zmiana initial value
  );

  async function updateName(prevState, formData) {
    try {
      const newName = await updateNameInDb(formData.get("name"));
      // nowy stan ustawiamy za pomocą return z akcji
      return { name: newName };
    } catch (error) {
      console.error(error.message);
    }
  }

  if (isPending) {
    return <p>Loading...</p>;
  }
  return (
    <>
      {/* zmiana na state.name */}
      <p>User: {state.name}</p>
      <form action={actionFunction}>
        <input type="text" name="name" required />
        <button type="submit">Send</button>
      </form>
    </>
  );
}

Dzięki temu podejściu możemy też trackować error w tym stanie, tak jak poniżej. Opieramy się na tym samym przykładzie, więc wskazuję tylko zmienione fragmenty:

const [name, actionFunction, isPending] = useActionState(
  updateName,
  { name: "Anon", error: null } // zmiana initial value
);

async function updateName(prevState, formData) {
  try {
    const newName = await updateNameInDb(formData.get("name"));
    return { name: newName, error: null };
  } catch (error) {
    console.error(error.message);
    // wykorzystanie prevState
    return { name: prevState.name, error: error.message };
  }
}

Feature 4. UseOptimistic.

Patrząc na statystyki komunikacji z serwerem, większość zapytań kończy się sukcesem, zwracając status 200.

Z tego powodu możemy śmiało korzystać z hooka useOptimistic w wielu miejscach naszej aplikacji. Pozwala nam on na natychmiastowe wprowadzenie zmian w interfejsie użytkownika, bez konieczności oczekiwania na zakończenie zapytania do serwera. W razie niepowodzenia, zmiany te zostaną automatycznie cofnięte, co upraszcza obsługę błędów i zapewnia płynniejsze doświadczenie użytkownika. Taka optymistyczna aktualizacja jest efektywnym podejściem w sytuacjach, gdzie prawdopodobieństwo niepowodzenia zapytania jest niskie.

Będziemy rozwijać poprzedni przykład:

import { useActionState } from "react";

function App() {
  const [name, actionFunction, isPending] = useActionState(
    updateName,
    { name: "Anon" } // zmiana initial value
  );

  // składnia jest bardzo podobna do hooka useState, mamy stan i funkcję do jego zmiany w nawiasach okrągłych podajemy stan (zmienną), którą hook ma obserwować
  const [optimisticName, setOptimisticName] = useOptimistic(state.name);

  async function updateName(prevState, formData) {
    const formName = formData.get("name");
    // wprowadzenie optymistycznej aktualizacji
    setOptimisticName(formName);
    // wysłanie zapytania na serwer
    try {
      const newName = await updateNameInDb(formName);
      return { name: newName };
    } catch (error) {
      console.error(error.message);
    }
  }

  if (isPending) {
    return <p>Loading...</p>;
  }
  return (
    <>
      {/* zmiana na optimisticName */}
      <p>User: {optimisticName}</p>
      <form action={actionFunction}>
        <input type="text" name="name" required />
        <button type="submit">Send</button>
      </form>
    </>
  );
}

Jak sam widzisz, w powyższym przykładzie użyliśmy hook useOptimistic, który które poprawia UX, pokazując użytkownikowi natychmiastowe zmiany. U nas ta “natychmiastowa zmiana” była dość trywialna, bo sprowadza się jedynie do wyświetlenia nazwy użytkownika, ale może ona dotyczyć również bardziej zaawansowanych scenariuszów. 

Takie optymistyczne aktualizacje możemy zaobserwować na Twitterze, na Discordzie i w wielu innych serwisach, gdy przykładowo wczytujemy jakieś dane i chcemy od razu pokazywać efekt użytkownikowi.

<p style={{color: isPending ? 'gray' : 'black'}}>User: {optimisticName}</p>

Feature 5. UseFormStatus.

useFormStatus to specjalny hook w React, który pozwala śledzić stan formularza (np. czy wysyłanie danych jest w toku). Jest on szczególnie przydatny, gdy chcemy dynamicznie aktualizować wygląd lub zawartość komponentów w zależności od tego, co dzieje się z formularzem – bez potrzeby ręcznego zarządzania stanem czy korzystania z bardziej zaawansowanych mechanizmów, takich jak Context. Hook ten automatycznie śledzi najbliższy formularz, w którym został użyty.

Przykład: Jeżeli mamy formularz z przyciskiem, który chcemy zablokować w momencie, gdy dane są wysyłane, możemy użyć useFormStatus, aby wykryć ten moment i zmienić tekst przycisku na "Submitting” (czyli "Wysyłanie...") w czasie wysyłania. Dzięki temu użytkownik ma jasną informację, że formularz jest przetwarzany i nie może go ponownie wysłać, dopóki operacja się nie zakończy.

Stwórzmy nowy komponent przycisku (MyButton) i użyjmy w nim hooka useFormStatus, aby automatycznie zmieniać jego stan w zależności od statusu formularza:

import { useFormStatus } from "react";

function MyButton({ children, ...rest }) {
  // pobieramy status formularza z hooka useFormStatus
  const { pending } = useFormStatus();
 
  // jeśli formularz jest w trakcie wysyłania (pending), zmieniamy tekst przycisku
  return <button {...rest}>{pending ? "Submitting..." : children}</button>;
}

W powyższym przykładzie komponent MyButton dynamicznie reaguje na status formularza, zmieniając tekst przycisku na “Submitting…” w momencie wysyłania danych. Dzięki temu hookowi, nie musimy ręcznie przekazywać stanu formularza do komponentu przycisku.

Zastosowanie w aplikacji.

Oto pełny przykład użycia tego komponentu w aplikacji. Formularz pozwala użytkownikowi na zmianę imienia, a w trakcie wysyłania danych aplikacja wyświetla odpowiedni komunikat oraz zmienia tekst na przycisku wysyłania:

function App() {
  // Używamy useActionState do obsługi formularza
  const [name, actionFunction, isPending] = useActionState(
    updateName,
    { name: "Anon" } // ustawiamy początkową wartość imienia
  );

  const [optimisticName, setOptimisticName] = useOptimistic(name);

  // Funkcja aktualizująca imię w bazie danych
  async function updateName(prevState, formData) {
    const formName = formData.get("name");
    setOptimisticName(formName);
    try {
      const newName = await updateNameInDb(formName);
      return { name: newName };
    } catch (error) {
      console.error(error.message);
    }
  }

  // Jeśli dane są w trakcie wysyłania, wyświetlamy komunikat "Loading..."
  if (isPending) {
    return <p>Loading...</p>;
  }

  return (
    <>
      {/* Wyświetlamy imię użytkownika */}
      <p>User: {optimisticName}</p>

      {/* Formularz do zmiany imienia */}
      <form action={actionFunction}>
        <input type="text" name="name" required />
       
        {/* Używamy komponentu MyButton wewnątrz formularza */}
        <MyButton type="submit">Send</MyButton>
      </form>
    </>
  );
}

W tym przykładzie formularz pozwala na aktualizację imienia użytkownika. Dzięki hookowi useFormStatus, przycisk MyButton zmienia tekst na "Submitting...", gdy dane są wysyłane, co daje użytkownikowi jasny sygnał, że operacja jest w toku.

Feature 6. Refs as props.

W React refy (refs) służą do przechowywania odwołania do elementów DOM lub komponentów oraz do przechowywania stanu, który nie powinien powodować ponownego renderowania komponentu. Innymi słowy, refy pozwalają na bezpośrednie manipulowanie elementami DOM (np. uzyskiwanie dostępu do przycisków, pól tekstowych itd.) bez konieczności wywoływania dodatkowych renderów.

Dlaczego refy są przydatne?

Przykładowe scenariusze użycia refów to:

1. Skupianie (fokus) na polach formularza.

2. Odczytywanie wartości z elementów DOM (np. z inputów) bez konieczności wyzwalania nowego renderu komponentu.

3. Manipulowanie elementami DOM, np. zmiana ich wyglądu, stylu lub zachowania.

W przeszłości, przekazywanie refów do niestandardowych komponentów w React mogło być trudne. Konieczne było użycie specjalnej funkcji forwardRef, aby ref mógł być przekazany do komponentu dziecka.

Jak to wyglądało wcześniej?

Zanim wprowadzono uproszczenia w React 19, musieliśmy używać mechanizmu forwardRef, aby umożliwić przekazywanie refów do komponentów. Oto przykład, jak wyglądało to w poprzednich wersjach React:

import { ReactNode, forwardRef } from "react";

type BtnProps = { children: ReactNode, type: "submit" | "button" };

// Używamy forwardRef, aby umożliwić przekazywanie refa do komponentu Button
export const Button = forwardRef<HTMLButtonElement, BtnProps>((props, ref) => {
  return <button ref={ref} type={props.type}>{props.children}</button>;
});

W powyższym kodzie, forwardRef pozwala nam na przekazanie refa do komponentu Button, który następnie przekaże go do elementu <button>. Aby użyć tego komponentu z refem, musieliśmy zdefiniować ref w aplikacji:

import { Button } from "./Button";
import { createRef } from "react";

type OtherComponentRef = ElementRef<typeof Button>;

const App = () => {
  const ref = createRef<OtherComponentRef>(null);

  return (
    <Button ref={ref} type="button">
      Click me!
    </Button>
  );
};

Feature 7. Uproszczenia w React 19.

W React 19 wprowadzono znaczne uproszczenia w przekazywaniu refów do komponentów. Teraz możemy przekazać ref bez potrzeby używania forwardRef, co sprawia, że cały proces jest znacznie prostszy i bardziej czytelny. Zamiast skomplikowanych mechanizmów, wystarczy przekazać ref jako zwykły prop:

function MyButton({ ref }) {
  return <button ref={ref}>test</button>;
}

function App() {
  const ref = useRef();

  return <MyButton ref={ref} />;
}

Jakie to ma znaczenie?

Dzięki tym zmianom w React 19, przekazywanie refów do komponentów jest teraz tak proste, jak przekazywanie dowolnych innych propsów. Oznacza to mniej kodu i mniej skomplikowanej logiki. W poprzednich wersjach musieliśmy korzystać z forwardRef, co było bardziej złożone i mniej intuicyjne. Teraz wystarczy zwyczajnie przekazać ref jako prop, co znacząco ułatwia pracę z refami.

Jak widać, w React 19 proces ten stał się o wiele prostszy, prawda? 😊

Feature 8. Funkcja use().

use() to nowa funkcja w React, która pozwala na obsługę asynchronicznych operacji (np. pobierania danych) bezpośrednio w ciele komponentu. Co istotne, use() NIE jest hookiem. To oznacza, że nie musi być używana zgodnie z regułami hooków (np. nie musimy jej wywoływać na samym początku komponentu lub w każdej renderowanej ścieżce). Dzięki temu możemy wywoływać use() w sposób warunkowy, czyli np. tylko wtedy, gdy jest to potrzebne.

Główne zastosowanie use()

use() jest używane do odczytywania asynchronicznych zasobów, takich jak dane z API. Automatycznie "zawiesza" (suspenduje) komponent, gdy dane nie są jeszcze dostępne, co oznacza, że React przerwie renderowanie komponentu, aż dane zostaną załadowane. Dzięki temu React może odświeżać dane asynchronicznie, bez potrzeby ręcznego zarządzania stanem ładowania.

Jedną z największych zalet tej funkcji jest to, że może zastąpić hooka useContext. Co więcej, w przeciwieństwie do tradycyjnych hooków, use() może być wywoływana warunkowo, np. tylko wtedy, gdy spełnione są pewne warunki.

Jakie są ograniczenia?

Obecnie, React w funkcji use() nie obsługuje obietnic (promise), które nie są cachowane. Oznacza to, że każda obietnica, którą wywołujemy, musi być wcześniej zapisana w pamięci podręcznej, aby uniknąć ponownego jej wywoływania przy każdym renderze komponentu. Jest to ważne, ponieważ gdybyśmy stworzyli nową obietnicę za każdym razem podczas renderowania, React musiałby ją za każdym razem ponownie realizować, co prowadziłoby do niepożądanych efektów, takich jak ponowne pobieranie tych samych danych. Dlatego musimy używać strategii cachowania danych, aby unikać tego problemu.

Zespół React pracuje nad rozwiązaniem tego ograniczenia, ale na chwilę obecną musimy stosować takie obejścia. W dokumentacji znajdziemy również wzmiankę na ten temat:

"Use does not support promises created in render."

Przykład użycia use()

Przyjrzyjmy się przykładowemu kodowi, który ilustruje, jak można używać funkcji use() do pobierania danych z API:

import { use, Suspense } from "react";

function FetchExample() {
  // Tworzymy obietnicę fetch, która pobiera dane z API
  const fetchPromise = fetch("url").then((res) => res.json());

  // Używamy funkcji use(), aby automatycznie obsłużyć asynchroniczne dane
  const data = use(fetchPromise);

  // Zwracamy interfejs oparty na pobranych danych
  return <p>{data.someProperty}</p>;
}

function App() {
  return (
    <Suspense fallback={<p>Loading...</p>}>
      {/* FetchExample jest "zawieszony", dopóki dane nie zostaną pobrane */}
      <FetchExample />
    </Suspense>
  );
}

Co się dzieje w tym przykładzie?

1. W funkcji FetchExample tworzymy obietnicę (fetchPromise), która pobiera dane z zewnętrznego API.

2. Następnie wywołujemy funkcję use(), która automatycznie zawiesza renderowanie komponentu, dopóki dane nie zostaną załadowane.

3. W komponencie App używamy komponentu Suspense, który pozwala na wyświetlenie rezerwowego interfejsu (w tym przypadku paragrafu z napisem “Loading...”), podczas gdy dane są pobierane.

Dlaczego to podejście nie działa jeszcze idealnie?

Obecnie ten kod nie działa poprawnie, ponieważ React nie obsługuje obietnic tworzonych bezpośrednio w renderze komponentu (np. w linijce const fetchPromise = fetch(...)). To dlatego musimy najpierw cache'ować obietnicę, aby uniknąć wielokrotnego wykonywania tej samej operacji przy każdym renderze.

Wnioski

Funkcja use() wprowadza nowy sposób pracy z asynchronicznymi danymi w React, ułatwiając pobieranie danych i zarządzanie ich stanem. Pomimo obecnych ograniczeń, już teraz widzimy, że use() znacząco upraszcza pracę z asynchronicznymi zasobami i otwiera nowe możliwości w przyszłych wersjach React.

Feature 9. Inne nowe funkcje w React 19.

Meta tagi wewnątrz komponentów.

Wcześniej, aby dynamicznie dodawać meta tagi (np. tytuł strony, autora, słowa kluczowe) z poziomu komponentów React, musieliśmy korzystać z zewnętrznych bibliotek takich jak react-helmet. Jednak w React 19 można teraz dodawać meta tagi bezpośrednio w komponencie, co upraszcza zarządzanie metadanymi.

W React 19 możemy dodawać meta tagi w taki sposób:

function BlogPost({ post }) {
  return (
    <article>
      <title>{post.title}</title>
      <meta name="author" content={post.author} />
      <meta name="keywords" content={post.keywords} />
    </article>
  );
}

W tym przykładzie widać, że możemy łatwo ustawić tytuł strony oraz meta tagi, takie jak autor czy słowa kluczowe, bez konieczności użycia dodatkowej biblioteki. Jest to prostsze i bardziej intuicyjne rozwiązanie.

React Server Components

To nowa funkcjonalność, która pozwala na renderowanie komponentów React na serwerze, jeszcze przed dostarczeniem ich do przeglądarki. Pozwala to na lepsze wsparcie dla renderowania po stronie serwera (SSR), znanego już z frameworków takich jak Next.js. Dzięki temu, części aplikacji mogą być renderowane na serwerze, co może przyspieszyć czas ładowania strony, zwłaszcza dla użytkowników z wolniejszymi połączeniami.

Komponenty serwerowe są wykonywane albo raz w czasie budowania aplikacji (build time), albo na żądanie dla każdego requestu klienta. To oznacza, że część logiki, która normalnie byłaby przetwarzana na urządzeniu użytkownika, jest rozwiązywana na serwerze, co zmniejsza obciążenie klienta.

UWAGA: React Server Components są nadal w fazie rozwoju, więc mogą wystąpić problemy z kompatybilnością między wersjami. Twórcy zalecają używanie canary release i przypinanie projektu do konkretnej wersji Reacta (np. 19.x), aby uniknąć niespodziewanych błędów.

Server Actions

Server Actions to nowa funkcja, która pozwala uruchamiać asynchroniczne operacje na serwerze z poziomu komponentów klienckich. Oznacza to, że komponent uruchamiany w przeglądarce może wywołać funkcję na serwerze, a wynik tej operacji zostanie zwrócony do komponentu.

Aby oznaczyć funkcję, którą chcemy uruchomić na serwerze, używamy specjalnej dyrektywy use server. Dzięki temu framework automatycznie generuje zapytanie do serwera, gdy funkcja zostanie wywołana z komponentu klienckiego. Jest to przydatne np. przy operacjach, które wymagają dostępu do bazy danych, a które nie powinny być wykonywane po stronie klienta.

// Funkcja uruchamiana na serwerze
"use server";

async function getServerData() {
  // Operacja, która musi być wykonana na serwerze
  const data = await fetchDataFromDatabase();
  return data;
}

UWAGA: Dyrektywa use server nie oznacza, że dany komponent staje się serwerowym komponentem. Jest to jedynie sposób na oznaczanie funkcji serwerowych, które można wywołać z poziomu komponentu klienckiego.

Nowe funkcje – przyszłość

Chociaż wiele z tych nowych funkcji (jak np. use() czy Server Actions) jest już dostępnych, w najbliższej przyszłości wiele z nich będzie automatycznie wspieranych przez twórców bibliotek i narzędzi. To oznacza, że nie wszystkie nowości wymagają od nas ręcznego wdrażania, a z czasem te funkcje staną się standardem w narzędziach do pracy z React.

Podsumowując, React 19 wprowadza wiele zmian, które mają na celu optymalizację aplikacji, ułatwienie pracy z asynchronicznymi zasobami oraz lepsze wsparcie dla renderowania po stronie serwera. Większość nowości może początkowo wydawać się skomplikowana, ale w dłuższej perspektywie sprawią one, że aplikacje React będą działać szybciej i efektywniej.

Sprawdź również nasz system mentorowania i outsourcowania specjalistów
  • Wyznaczona ścieżka od A do Z w kierunku przebranżowienia
  • Indywidualnie dostosowany materiał pod ucznia
  • Code Review ze strony mentora
  • Budowa portfolio
  • Intensywna praca z materiałami szkoleniowymi
Zapytaj