Галоўная / Артыкулы / Трыяж апгрэйда React 19: Калькі новыя API заменяюць існуючыя альтернатывы

Трыяж апгрэйда React 19: Калькі новыя API заменяюць існуючыя альтернатывы

Практычны ўзгляд на выкарыстоўванне Server Actions, useOptimistic і React Compiler, з важлівымі застерэжаннямі та планамі ўсёх змян у React 19, якія трэба першыя застосаваць.

1579 слоў

React 19 адаптава большыя паверхні API, чым будзь-яя версія за апошнія гады, і такая маса новых можнасцей ускладнюе выбір таго, што справды важна для існуючай базы коду. Для большасці команд калькі новых функцыяй заменяюць існуючыя способы рашэння проблем, адна з яных змінюе падход да завантажэння дадзеных, а рэшты можна зачакаць. Ёсць пераказы таго, што справды важна, з прыкладамі коду до і пасля адаптации, а таксама з прытаманнасцямі, якія лёгка занедбаць, ўпрынцыпе вы маглі б планаваць адаптацию на адзыякшчыну ўжоць ціннасці.

use: чытанне Promises і Context пад час render

API use ёсць найзначнейшым даданнем, і яго працаванне адрозніцаеся ад усіх вядомых вам хуков. Звычныя хуки павінны быць вызываны на верхнім роўні компонента, у той самы порядак пад кожным render. use не падпадае під гэтыя правіла: яго можна вызываць пасля раннега return, унутры умовы або унутры ціклу.

У прыкладзе нижэй компанента вяртае адразу адзінаковы выгляд, калі няма userId, і толькі пасля чаго чытае данні пользователя:

// React 19 — use() can be called inside conditionals and loops
function UserProfile({ userId }) {
  if (!userId) return <GuestView />;

  // This is valid in React 19
  const user = use(fetchUser(userId));

  return <div>{user.name}</div>;
}

Настоямая сіла крыўця use заключаецца ў тым, што яно можа прыймаць: Promise або Context. Калі прадастся Promise, React призупіняе роботу компанеты пакуль яна не будзе выкарыстоўвана. Нижэйшая парадына паказвае, сколькі змянюецца. У старэйшай версіи данні і флаг загрузкі выкарыстоўваліся ў стацыяні, данні запрашваліся за дапамогою effect, а спінер рэндарываўся вручную; у версіі React 19 значэнне чытаецца безпосередна:

// Before React 19
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchUser(userId).then(data => {
      setUser(data);
      setLoading(false);
    });
  }, [userId]);

  if (loading) return <Spinner />;
  return <div>{user.name}</div>;
}

// React 19
function UserProfile({ userId }) {
  const user = use(fetchUser(userId));
  return <div>{user.name}</div>;
}

Робота проста перайшла іншым шляхам, а не зникла: найбліжэйшыя межы <Suspense> паказваюць альтернатыўны выгляд, пакуль Promise ў стане чакання, а найбліжэйшыя межы для обработкі памылак караюцься ў разе адхілення. Компанента описвае толькі шлях успеху.

Promise должен быть стабільным

use чакае той самы Праміс у кожны раз, калі відбываецца рендары. Якщо fetchUser(userId) створае новы Праміс у кожны раз пад час рендару, React знову призупіняе обробку кожны раз, што спрычынае паўтаральныя запиты або компонент, які ніколі не досягае стану готовнасці; React паведамляе пра Прамісы, якія не зберагаюцца у кэшы пад час рендару ў кліентскіх компонентах. Таму трэба спрыяглядаць на этыя прыклады як на ілюстраціі формату. Праміс павінен походзіць з чаго-небудзь, што яго кэшуе: бібліятэкі дадзеных, якая падтрымляе Suspense, ладчыка фрэймворку або серверскага компонента, які запускае запит і перадае Праміс як проп. Іноды радзяць загортваць вызов у useMemo, але React не гарантуе, што мемузаваныя значэння будуць зберагваны, таму гэта не ўпевненны кэш.

Дзеяння на серверы як фіча React

Пользоватары Next.js App Router вже знаюць прыемкі Server Actions. У React 19 яны сталі частью самага React (у дасягліх тэкстах іх тепер называюць Server Functions) і ўдоступныя ў будзь-якім фрэймворку, який падтрымлеў Server Components.

У прыкладзе задана асінхронная функцыя, пазначаная атрыбутам 'use server', якая дадае новага пользователя і павтапрацоўвае спіс, а потым перадае яе ў атрыбут action формы:

// Server Action — runs on the server, called from the client
async function submitForm(formData) {
  'use server';

  const name = formData.get('name');
  await db.users.create({ name });
  revalidatePath('/users');
}

// Client component
function UserForm() {
  return (
    <form action={submitForm}>
      <input name="name" />
      <button type="submit">Add User</button>
    </form>
  );
}

Атрыбут action тэга <form> тепер можа прымець функцыю, включаючы асінхронную; React запускае яе пад час надсылкі даных і перадае яй FormData. Статусы у паўпрацоўкі, оптымістычныя апдэйты і падзеі з багамі выкарыстоўваюць супутнія API: useFormStatus або useActionState для статусаў у паўпрацоўкі і рэзультатаў, useOptimistic для мглевага адзыву, а таксама бар’еры для обрабоцы падзеяў з багамі. Вы пішаеце код для змены даных; React координуе рэшту.

У рэальным прыкладе неабяжна практычная правка ў адным з элементаў куска коду. Функцыя з адразовай дырэктываў 'use server' можа быць вядомая толькі ў Компаненте сервера. Якщо UserForm ўжо ёсць Компанентам кліента, перыявіце submitForm у саблонны файл, дзе на пачатку будзе 'use server', і запрашайце яго.

Дзе Компаненты сервера адключаюць статычны UI з пакета кліента, Компаненты дзеяння сервера адключаюць ручна напісаныя маршруты API для змэн: меней коду, меней перадачаў даных. Усё роўна трэба спрыяглядаць кожнае дзеяння як публічны канцэнтр і пераканацца, што вхідныя даны і правы на выкананне ўсё правильна.

useOptimistic: мглевыя адпаведзі без дублявання статаусу

Раней для адаптавання рэзультата даўна перад пאдтверждэнням сэрвера было неабходна ручна кераваць дадатковым станам. Функцыя useOptimistic берае текущы стан і функцыю апдэйта, падобную редукеру, і вяртае стан, які трэба адобразіць, плюс функцыю, якая прыменяе оптымістичныя змены. У гэтым прыкладзе, дадаўшы новую задачу, ствараецца тымчасовы элемент, пазначаны як «у адліку», який адобразаецца з легкай затуманенасцю пакуль не завершыцца запіс:

function TodoList({ todos }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (currentTodos, newTodo) => [...currentTodos, newTodo]
  );

  async function handleAdd(text) {
    addOptimisticTodo({ id: 'temp', text, pending: true });
    await saveTodo(text); // Server Action
  }

  return (
    <ul>
      {optimisticTodos.map(todo => (
        <li key={todo.id} style={{ opacity: todo.pending ? 0.7 : 1 }}>
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

Калі вы вызываеце addOptimisticTodo, React негайна адобразае список з оптымістичнымі даннымі. Калі завершаецца супакоўваныя дзеяння, оптымістичныя даны зношваюцца, і React знову адобразае даны з пропа todos, які ў тым часе вялікай шанцай уже мае запісаны элемент.

Раней вы зберагалі падтверджаныя та чакаючыя копіі дадзейнаў і ручна ўсё чыставалі, калі яны разлічваліся; тепер гэты цыкл жыцця карае сам прыемальнік дадзейнаў. Важныя два ўмовы. Першая — оптымістычны апдэйт павінен выконвацца ў момант адпаведной дзеяння або пераходу, напрыклад у функцыі, якая пасылана ў атрыбут action формы або загорнута ў startTransition; яе вызыв з звычайнага обробніка запалоў заставляе React павесты паведамленне пра проблему, і апдэйт можа не паказацца. Другая — родны элемент павінен фактычна прымець свежыя даны todos пасля зберагання, як правіла, через перапрацоўку, інакш новы элемент зникне, калі оптымістычны стан будзе скасаваны. Тымчасовыя ідэнтыфікаторы, такія як 'temp', таксама можу стварыць конфлікты, якщо два элементы дадацца быстра, таму трэба ствараць унікальны ідэнтыфікатор. Мы розглядаем гэтыя крайнія случаў у пяці способах абвалкання з викорыстаннем оптымістычнасці.

Кампайляр React: мемаізацыя як стандарт

Кампайляр React, ранейша версія якого называлася React Forget, з’явіўся разам з React 19 як необов’язковы крок падчас будування проекта, і ён мае найбольшы вплыв на повсякдзенны код. Ён дагаўляе мемаізацыю пад час будування, таму значэнні і функцыі-вызыванні пераўтвараюцца знову, калі вхідныя даны застаюцца незменнымі, а дзецявы элементы не перераскладаюцца, калі пропсы застаюцца тымі ж. Ручныя функцыі useMemo, useCallback і React.memo стаюць пераважна непатрэбнымі, як паказвае порэванне:

// Before: manual memoization required
const expensiveValue = useMemo(() => compute(a, b), [a, b]);
const stableCallback = useCallback(() => doSomething(id), [id]);
const MemoizedChild = React.memo(ChildComponent);

// After React Compiler: write normal code
const expensiveValue = compute(a, b);
const handleClick = () => doSomething(id);
// ChildComponent renders only when its props change - automatically

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

Для новых проектаў кампайлер ёсць надзеямы стандарт. У існуючам коде ўводзіце яго астаточна адзячна: ён прыпускае, што компаненты ў чыстай форме і адпавядаюць правілам React, таму код, які змінюецца пад час рэндара, можа праявляць сабе інача пасля кампайліравання. Увядзіце яго паступова, запускайце тэсты і спачатку вядзіце пошук наявных адхылэнняў за дапамогою плагіна ESLint. Пераканаўцеся ў дакументацыі React пра статус выдання і падтрымваныя версіі React.

Што застосаваць і ў какім порядку

Негараздзявыя выгоды, низкі рызык

  • useOptimistic там, дзе формы вяртаюцца зв’язком з станам сервера.
  • Server Actions, якщо вы викорыстоўвайце Next.js 14 або новейшую версію і хочаце заменіць API-маршруты на мутацыі.

Што варта запланаваць

  • use, калі ваш слой дадзеных стварае стабільныя, кэшаваныя Promises; ён гармоніюе з Suspense.
  • У новых проектах — React Compiler, а ў вялікай меры апробаваных частках існуючага дапрыяву — спачатку.
  • Для большасці дапрыяву гэта не ўскорана патрэба

    • Змены ў ref: тепер ref можа быть пераданы як звычайны проп у функцыйныя компоненты, таму forwardRef больш не патрэбен.
    • Удосконаленні контексту, якія ў большай меры стосуюцца паспрабоў развіяльніка, а не новага паведання.
    • Падтрымка метадаў дакументацыі, якая дазволяе компонентам атрыбутаваць тагі <title> і <meta>, якія React размешчае ў чырвонцы документа.

    Галоўныя выводы

    • React 19 — это эвалюяцыя, а не кардынальная перапісва. Яго новыя API формалізуюць патэрны, якія команды разработчиков вже ствараюць вручную: оптымістычныя апдэйты, серверскія мутацыі і умовнае чытанне дадзеных.
    • use пераносіць процес завантажэння і обробку памылак у Suspense і межы памылак, але ён працюе належна толькі з Promises, якія кэшуюцца за межамі процесу рэндараўвання.
    • Server Actions і useOptimistic гармонійна супрацоўнуюць: першыя адпаведна керуюць запісамі, а другія маскуюць ўплошчэння часу адпаведных дзеянь.
    • Кампіляр ставіць мемаізацыю ў стан дыфалту, за аднальнай умовы, што вашы компоненты выпалняюць правіла React.

    Патрэбны вопыт — не тое, чы хацець апгрэйдаваць, а тое, якая з эых прымітываў можа заменіць тое альтернатыўнае рашэнне, якое вы зараз викорыстоўваете. Пачніце з гэтага.

    Спадневаная література

  • Асінхронныя формы ў React 19 з use, useActionState і useOptimistic — Как use(), useActionState, useFormStatus і useOptimistic заменяюць ручнае завантажэнне і пазначкі абохвот у React 19, а таксама якія прынцыповыя недагадкі крыюцься за кожным з гухаў.