Strona główna / Artykuły

Usuwanie otoczki dostawcy: kontekst renderowania z komponentów serwerowych

Dowiedz się, w jaki sposób React 19.3 umożliwia komponentowi serwerowemu bezpośrednie renderowanie kontekstu klienta, jak Flight go serializuje oraz jakie ograniczenia dotyczące odczytu i wartości nadal obowiązują.

Wydawca: REACTAPP.TOP7 min czytania1602 słów

Źródła: React 19.3: Render a Context Directly in a Server Component, No Provider Wrapper

Prawie każda baza kodu React Server Components zawiera mały plik 'use client', którego jedynym zadaniem jest utworzenie kontekstu oraz eksportowanie komponentu dostawcy, który przekazuje jeden parametr. Ten otoczak istniał dlatego, że komponent serwerowy nie mógł samodzielnie renderować kontekstu. React 19.3 znosi tę ograniczenie, dzięki czemu serwer może teraz bezpośrednio renderować element kontekstu, a otoczak można usunąć.

To przewodnik omawia stary wzorzec, nowy wzorzec, to, co tak naprawdę robi protokół Flight w tle, oraz pułapki, które decydują o tym, czy ta zmiana będzie działać w twoim frameworku obecnie. Przykłady wymagają rzeczywistego środowiska działania Server Components (frameworku z obsługą bundlera RSC oraz serwera), więc traktuj je jako kompletny kod referencyjny, a nie coś, co należy wkleić do środowiska testowego. Aby dowiedzieć się więcej na temat tego, jak łączą się część serwerowa i kliencka aplikacji RSC, zapoznaj się z naszym przeglądem architektury stojącej za React Server Components.

Stary wzorzec: komponent, który jedynie przekazuje właściwość

Komponenty serwerowe nie mogą wywoływać funkcji createContext, ponieważ Context to mechanizm zarządzania stanem po stronie klienta. Mimo to drzewo komponentów klienta regularnie potrzebuje danych, które serwer już posiada, takich jak zalogowany użytkownik, flagi funkcjonalne nabywcy lub lokalizacja żądania. Jedynym sposobem na przekazanie tych danych było użycie właściwości prop komponentu klienta, który następnie renderował element provider.

Moduł klienta zaczyna się od standardowej dyrektywy i importu.

'use client';
import { createContext } from 'react';

Następnie tworzy się Context i eksportuje provider, którego treść nie robi nic poza przekazaniem wartości currentUser jako value.

export const UserContext = createContext(null);// exists only to forward one prop
export function UserProvider({ currentUser, children }) {
  return (
    <UserContext value={currentUser}>
      {children}
    </UserContext>
  );
}

Strona komponentów serwerowych

Layout, będący komponentem serwerowym, importuje wrapper zamiast Context.

import { UserProvider } from './user-context';

Na serwerze pobiera się informacje o użytkowniku i przekazuje je do wrappera jako właściwość prop.

export async function Layout({ children }) {
  const currentUser = await getCurrentUser();
  return (
    <UserProvider currentUser={currentUser}>
      {children}
    </UserProvider>
  );
}

Jeśli spróbujesz pominąć warstwę otaczającą w React 19.2, napotkasz mylący błąd w czasie wykonywania, który zaczyna się od frazy „Element type is invalid”, a następnie informuje o tym, że typ elementu typu lazy został rozwiązany jako Context zamiast jako klasa lub funkcja. Wydaje się to problemem bundlera, ale przyczyna leży w rendererze. Context importowany poza granice klienta dociera do renderera serwera jako odniesienie klienta, które jest rozwiązywane w trybie lazy, a wersja 19.2 mogła przekształcić takie odniesienia jedynie w komponenty. Obiekt Context nigdy nie był akceptowanym wynikiem.

Nowy wzorzec: renderowanie samego Context

W wersji 19.3 moduł klienta skraca się do dyrektywy, importu i pojedynczego exportu.

'use client';
import { createContext } from 'react';

Nie ma już żadnego komponentu, jest tylko obiekt Context.

export const UserContext = createContext(null);

Renderowanie z layoutu

Layout teraz importuje bezpośrednio UserContext.

import { UserContext } from './user-context';

JSX ma taką samą strukturę jak wcześniej, z tą różnicą, że elementem jest Context, a właściwość nazywa się value.

export async function Layout({ children }) {
  const currentUser = await getCurrentUser();
  return (
    <UserContext value={currentUser}>
      {children}
    </UserContext>
  );
}

Korzystanie z niego na stronie klienta

Dla użytkowników nic się nie zmienia. Komponent klienta umieszczony gdziekolwiek poniżej struktury layoutu importuje ten sam Context i odczytuje go za pomocą use.

'use client';
import { use } from 'react';
import { UserContext } from './user-context';

Tutaj menu z awatarami pobiera użytkownika z Context i wyświetla jego obraz.

export function AvatarMenu() {
  const user = use(UserContext);
  return <img src={user.avatar} alt={user.name} />;
}

Zwróć uwagę na składnię React 19 w layoutie: wyświetla się tam <UserContext value> zamiast <UserContext.Provider value>. Forma z .Provider nadal działa na stronie klienta, ale serwer wyświetla sam obiekt Context, więc w kodzie serwerowym należy używać krótszej formy.

Co robi Flight i czego ta funkcjonalność nie robi

Gdy komponent serwerowy renderuje <UserContext value={currentUser}>, na serwerze w ogóle nie uruchamia się żadna logika związana z Contextem. Serwerializator Flight emituje element typu odniesienie do klienta, wskazujące na moduł user-context.js oraz jego eksport UserContext. Element ten zawiera jako swoje właściwości { value: currentUser, children }. Te właściwości są serwerializowane dokładnie tak samo jak każde inne właściwości przekazywane do klienta. Gdy przeglądarka załaduje ten moduł, odniesienie do klienta zamienia się w rzeczywisty obiekt Context, a React tworzy dostawcę przechowującego przesłaną wartość. Poprawka dotycząca renderera, odnotowana w facebook/react #35675, sprawiła, że ścieżka kodu typu lazy-type może przyjmować obiekt Context oprócz funkcji, memo i forwardRef.

Z takiego projektu wynikają dwie konsekwencje.

Wartość musi być serwerializowalna

Wartość Context podlega tym samym zasadom co każda inna właściwość przekazywana do klienta. Dozwolone typy to prymitywy takie jak łańcuchy i liczby, zwykłe obiekty i tablice, instancje Date, Map i Set, obietnice (promises), elementy JSX oraz funkcje serwerowe. Zwykłe funkcje, instancje klas i wszystko, co ma własny prototyp, nie są dozwolone. Zwykły zapis currentUser działa; instancja UserService z metodami nie. Pełną listę przedstawia dokumentacja 'use client'.

To nie jest Context po stronie serwera

W prośbie o pull request jasno stwierdzono, że nadal nie można wywołać use(Context) wewnątrz komponentu serwerowego. Komponenty serwerowe nie mają możliwości odczytania Context, ponieważ na serwerze nie istnieje żaden dostawca do jego odczytu. Ta funkcjonalność służy wyłącznie do skrócenia drogi przekazywania wartości do drzewa klienta.

Dlaczego warte jest usunięcie otoczki

Każda współdzielona wartość wymagała wcześniej jednego pliku i jednego komponentu. Wydaje się to drobnostką, dopóki ich nie policzymy: aplikacja z kontekstami użytkownika, tematem, lokalizacją, flagami funkcji, tenantem oraz danymi analitycznymi zawiera sześć niemal identycznych otoczeń, a każde z nich stanowi możliwość zapomnienia jakiejś właściwości lub błędnego wpisania wartości domyślnej. Dzięki nowej formie dostawca znajduje się dokładnie tam, gdzie pobierane są dane, a koncepcja „które części działają na serwerze, a które na kliencie” traci jeden szczególny przypadek.

Reakcja społeczności na tę zmianę była głównie ulgą, a nie entuzjazmem. Programiści zauważyli, że dziwne jest to, iż coś takiego w ogóle nie było obsługiwane, skoro wartość kontekstu to właściwie tylko dane przechowywane w pamięci. Ta intuicja jest słuszna i tłumaczy, dlaczego poprawka w samym React była niewielka.

Kwestie do uwzględnienia przed migracją

  • Twoje framework musi zawierać paczki Flight 19.3. Zmiana dotyczy plików react-server-dom-webpack, -turbopack oraz -parcel wraz z komponentem reconciler, a nie tylko pliku react. Sprawdź wersję Reactu, którą ustala twoje framework – niezgodność może spowodować ten sam błąd „Element type is invalid”, o którym mówiono wcześniej.
  • Zdefiniuj Context w pliku oznaczonym jako 'use client'. Context zdefiniowany w kodzie serwerowym nie może zostać w ten sposób wyświetlony, ponieważ cały mechanizm opiera się na obecności tego obiektu w pliku klienta.
  • Czytanie danych na serwerze nadal jest niemożliwe. Komponent serwerowy, który potrzebuje danej wartości, powinien otrzymać ją jako argument lub sam ją pobrać; użycie use(Context) w takim przypadku powoduje błąd.
  • Działają tylko wartości serializowalne. Jeśli stara wartość była instancją klasy lub miała dołączone metody, otoczka je ukrywała, a problem pojawia się teraz jako błąd serializacji.
  • Obietnica jest wartością dopuszczalną do użycia. Ponieważ obietnice mogą być serializowane między różnymi obszarami, zapis <UserContext value={getCurrentUser()}> bez słowa kluczowego await przesyła wartość strumieniowo, a odbiorcy muszą ją dwukrotnie rozwijać za pomocą use(use(UserContext)). Może to być przydatna technika strumieniowania, ale łatwo też popełnić błąd poprzez pominięcie await.
  • Wartości domyślne pozostają sprawą klienta. Wartość domyślna przekazana do createContext(null) jest stosowana tam, gdzie nad odbiorcą nie ma żadnego dostawcy, a renderowanie dostawcy na serwerze tego nie zmienia.
  • Pozostanie na razie przy otoczce

    Jeśli twoje framework nie przyjąło jeszcze wersji 19.3, zachowaj funkcję otaczającą. Wzorzec UserProvider działa we wszystkich wersjach React 19 oraz we wszystkich frameworkach. Gdy dokonasz aktualizacji, migracja jest prosta:

    • Usuń funkcję otaczającą z modułu klienckiego.
    • Importuj Context do komponentu serwerowego zamiast funkcji otaczającej.
    • Zmień nazwę przekazywanej właściwości na value.

    Możesz migrować plik po pliku w miarę, jak twoje framework przechodzi na wersję 19.3. Nie ma potrzeby konwertować wszystkiego naraz, a gdy obie formy będą działać, nie będzie między nimi różnicy pod względem wydajności w czasie działania.

    Główne wnioski

    • W React 19.3 komponent serwerowy może renderować Context eksportowany z modułu 'use client', więc funkcje otaczające dostawców o jednoznacznym przeznaczeniu stają się niepotrzebne.
  • W rzeczywistości Flight wysyła odniesienie do klienta wraz z zserializowanymi właściwościami; na serwerze nie uruchamia się żadne zachowanie Context.
  • Wartość musi spełniać standardowe zasady serializacji, a komponenty serwerowe nadal nie mogą odczytywać Context.
  • Zanim usuniesz otulacze, upewnij się, że twoje frameworki zawierają odpowiednie pakiety Flight, i uważaj na przypadkowe wartości typu promise.
  • Aby uzyskać więcej informacji, przeczytaj notatki o wydaniu React 19.3 na stronie react.dev oraz dokumentację dotyczącą komponentów serwerowych.