Головна / Статті

Відмова від обгортки провайдера: контекст відображення з компонентів сервера

Дізнайтеся, як React 19.3 дозволяє серверному компоненту безпосередньо відображати клієнтський контекст, як Flight його серіалізує, та які обмеження щодо читання та значень все ще діють.

Видавець: REACTAPP.TOP7 хв читання1602 слів

Джерела: React 19.3: Render a Context Directly in a Server Component, No Provider Wrapper

Майже кожен кодовий базис React Server Components містить невеликий файл 'use client', єдиною функцією якого є створення Context та експорт компонента-постачальника, який передає один проп. Цей обгортковий компонент існував тому, що серверним компонентам не дозволялося безпосередньо відображати сам Context. У React 19.3 ця обмеження було скасовано, тож сервер тепер може безпосередньо відображати елемент Context, і обгортковий компонент можна видалити.

Цей посібник розглядає стару схему, нову схему, що насправді робить протокол Flight у своїй суті, а також потенційні проблеми, які визначають, чи буде зміна функціонувати у вашій фреймворк-системі сьогодні. Для прикладів потрібен справжній середовище виконання Server Components (фреймворк із підтримкою бандлера RSC та сервером), тому сприймайте їх як повний код для посилання, а не як щось, що можна вставити у сандбокс. Щоб дізнатися більше про те, як серверна та клієнтська частини додатку RSC взаємодіють, перегляньте наш огляд архітектури React Server Components.

Стара схема: компонент, який лише передає проп

Компоненти сервера не можуть викликати createContext, оскільки Context є механізмом керування станом на боці клієнта. Проте дерево компонентів клієнта регулярно потребує даних, які вже є на сервері, таких як авторизований користувач, флаги функціональності тенанта чи локалізація запиту. Єдиним способом передачі даних через цей кордон були пропси компонента клієнта, який потім відображав компонент provider.

Модуль клієнта починається зі звичайної директиви та імпорту.

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

Потім створюється Context та експортується provider, чий код не робить нічого, окрім як передавати currentUser у ролі value.

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

Боку компонента сервера

Лейаут, який є компонентом сервера, імпортує обгортку замість Context.

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

На сервері отримується інформація про користувача, яка потім передається обгортці у вигляді пропса.

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

Якщо ви спробуєте пропустити обгортку у React 19.2, ви зіткнетеся з заплутаною помилкою під час виконання, яка починається з фрази „Element type is invalid“, а потім повідомляє про те, що тип лендж-елемента було отримано у вигляді Context замість класу чи функції. Це схоже на проблему з бандлером, але причина криється у рендерері. Context, імпортований через межу клієнта, потрапляє до серверного рендерера як клієнтський посилання, яке обробляється з відстрочкою, і версія 19.2 могла перетворити такі посилання лише на компоненти. Об’єкт Context ніколи не вважався прийнятним результатом.

Новий підхід: рендерувати сам Context

У версії 19.3 клієнтський модуль скорочується до директиви, імпорту та одного експорту.

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

Компонента більше немає — залишився лише об’єкт Context.

export const UserContext = createContext(null);

Його рендерування з лейауту

Тепер лейаут безпосередньо імпортує UserContext.

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

JSX має таку саму структуру, як і раніше, за винятком того, що елемент — це Context, а властивість називається value.

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

Використання на клієнті

Для користувачів нічого не змінюється. Клієнтський компонент, розташований будь-де нижче лейауту, імпортує той самий Context та читає його за допомогою use.

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

Тут меню з аватаром отримує дані користувача з Context та відображає зображення.

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

Зверніть увагу на синтаксис React 19 у лейауті: він відображає <UserContext value> замість <UserContext.Provider value>. Формат .Provider все ще працює на клієнті, але сервер відображає сам об’єкт Context, тому у коді для сервера використовуйте скорочений формат.

Що робить Flight та що ця функція не є

Коли компонент сервера відображає <UserContext value={currentUser}>, на сервері зовсім не виконується жодна логіка контексту. Серіалізатор Flight випромінює елемент, який є посиланням на клієнт, що вказує на модуль user-context.js та його експорт UserContext. Цей елемент містить у своїх властивостях { value: currentUser, children }. Ці властивості серіалізуються точно так само, як і будь-які інші властивості, що надсилаються на клієнт. Як тільки браузер завантажить цей модуль, посилання на клієнт перетворюється на справжній об’єкт Context, і React створює провайдера, який зберігає передане значення. Виправлення для рендерера, задокументоване у facebook/react #35675, навчило шлях коду з лінивим типуванням приймати об’єкт Context окрім функцій, memo та forwardRef.

З цієї конструкції випливають два наслідки.

Значення має бути серіалізованим

Значення Context підкоряється тим самим правилам, що й будь-яке значення, яке надсилається на клієнт. Дозволені типи включають примітиви, такі як рядки та числа, звичайні об’єкти та масиви, екземпляри Date, Map та Set, обіцянки (promises), елементи JSX та функції сервера. Звичайні функції, екземпляри класів та все, що має власний прототип, не дозволені. Простий запис currentUser працює; екземпляр UserService із методами — ні. У документації 'use client' наведений повний список.

Це не Context з боку сервера

У запиті на pull request чітко зазначено, що всередині компонента сервера все ще неможливо викликати use(Context). Компоненти сервера не мають можливості читати Context, оскільки на сервері відсутній постачальник (provider), який би їм це дозволив. Ця функціональність є лише скороченим способом передачі значення до дерева клієнта.

Чому варто прибрати обгортку

Кожна спільна змінна раніше вимагала одного файлу та одного компонента. Це здається формальністю, поки ви їх не порахуєте: додаток із контекстами для користувача, темою, локалізацією, флагами функцій, тенантом та аналітикою містить шість майже ідентичних обгорток, і кожна з них — це можливість забути якийсь параметр чи помилково ввести значення за замовчуванням. Завдяки новій структурі постачальник знаходиться прямо там, де отримуються дані, і ментальна модель „які частини працюють на сервері, а які — на клієнті“ втрачає один особливий випадок.

Реакція спільноти на цю зміну була переважно полегшенням, а не захопленням. Розробники зазначили, що дивно було те, що це спочатку не підтримувалося, адже значення контексту насправді є просто даними, які зберігаються в пам’яті. Ця інтуїція правильна, і вона пояснює, чому сама корекція в React була незначною.

Проблеми перед міграцією

  • Ваша фреймворк-система повинна постачати пакети Flight версії 19.3. Зміни стосуються react-server-dom-webpack, -turbopack та -parcel, а також компонента reconciler, а не лише react. Перевірте версію React, яку встановлює ваша фреймворк-система; неузгоджені версії призводять до тієї самої помилки „Element type is invalid“, про яку йшлося раніше.
  • Визначайте Context у файлі, позначеному 'use client'. Context, визначений у коді сервера, не може бути відтворений таким чином, оскільки весь механізм залежить від наявності цього об’єкта у клієнтському бандлі.
  • Читання даних на сервері все ще неможливе. Компонент сервера, який потребує цих даних, повинен отримати їх як аргумент або самостійно їх отримати; використання use(Context) у такому випадку призводить до помилки.
  • Функціонують лише серіалізовані значення. Якщо попереднє значення було екземпляром класу або мало приєднані методи, обгортка їх приховувала, і тепер проблема проявляється як помилка серіалізації.
  • Обіцянка є допустимим значенням. Оскільки обіцянки серіалізуються через межі, запис <UserContext value={getCurrentUser()}> без await передає значення в потік, і споживачі повинні розгорнути його двічі за допомогою use(use(UserContext)). Це може бути корисною технікою потокової обробки, але також легко припуститися помилки, пропустивши await.
  • Значення за замовчуванням залишаються справою клієнта. Значення за замовчуванням, передане до createContext(null), застосовується там, де над споживачем немає постачальника, і рендеринг постачальника на сервері це не змінює.
  • Наразі залишатися з обгорткою

    Якщо ваша фреймворк-система ще не перейшла на версію 19.3, залиште обгорткову функцію. Шаблон UserProvider працює у кожній версії React 19 та в будь-якому фреймворку. Коли ви все ж таки оновите систему, міграція буде простою:

    • Видаліть функцію-обгортку з клієнтського модуля.
    • Імпортуйте Context у серверному компоненті замість обгортки.
    • Перейменуйте передану властивість на value.

    Ви можете мігрувати файли по одному, у міру того як ваша фреймворк-система переходить на версію 19.3. Немає потреби конвертувати все одночасно, і як тільки обидві версії будуть працювати, між ними не буде різниці під час виконання.

    Основні висновки

    • У React 19.3 серверний компонент може відображати Context, експортований з модуля 'use client', тому обгортки-постачальників з однією функцією стають непотрібними.
  • Під капотом Flight надсилає посилання на клієнта та серіалізовані параметри; жодна логіка Context не виконується на сервері.
  • Значення мають відповідати стандартним правилам серіалізації, і Server Components все одно не можуть читати Context.
  • Переконайтеся, що ваша фреймворк-система об’єднує відповідні пакети Flight перед видаленням обгорток, та стежте за випадковими значеннями promise.
  • Для деталей ознайомтесь із примітками до випуску React 19.3 на react.dev та довідкою щодо Server Components.