Асінхронныя формы ў React 19 з use, useActionState і useOptimistic
Як use(), useActionState, useFormStatus і useOptimistic заменяюць ручна завантажэнне і пазнакі ад бягаў у React 19, а таксама якія небяглівыя проблемы кожны з гухоў таямніча лячыць.
Даўнае раней чым React 19, практычна кожны запит або форма выклікала неабходнасць у вжыцці аднаковых элементаў базавання: useState для зберагчыць дадзеныя, useEffect для ўтварэння запыту, а таксама флагі для пазначэння статусу завантажэння, наявнасці адзінакоў і рэзультатаў. React 19 дагаўляе чатыры API, якія пераносяць гэтыя функцыі ў саму рамку: use(), useActionState, useFormStatus і useOptimistic. Хто што робіць, як гэтыя элементы взаімаўзаеся і якія абмежэнні лёгка працягнуць — усе гэта паказана. Для болей шырокага агледку першых крокаў пад час апдэйта, адзірніце React 19 upgrade triage.
Чытанне promise-ў пад час рэндараў з вжыццю use()
use() — гэта не хук у традыцыйным сэнсе. На вяроць хукам, яго можна вызваць унутраніх умов, циклаў або пасля прытварнага вярнення. Ён чытае значэнне Promise або Context пад час рэндараўання і супрацоўвае з Suspense, каб паказаць стан чакання.
import { use, Suspense } from "react";
UserCard прымеў promise і распакоўвае яго за дапамогою use; пакуль ён не будзе рашыты, найбліжэйшая межа Suspense у Page атрымлеяе яго альтэрнатыўны варыянт.
function UserCard({ userPromise }) {
const user = use(userPromise);
return <h2>{user.name}</h2>;
}function Page({ userPromise }) {
return (
<Suspense fallback={<p>Loading user…</p>}>
<UserCard userPromise={userPromise} />
</Suspense>
);
}
Родны элемент стварае promise, дзецінскі элемент яго выкарыстоўвае, а Suspense керуе процэсам завантажэння. Існуе адна важлівая умова: promise павінен быць стабільным. Promise, створаны пад час рэндараўання кліентскага компонента, стае новым ў кожны раз пад час рэндараўання і спрычынае павтаральнае завішэнне, таму яго трэба ствараць у серверскім компоненте, у кэшаваным загрузчыку або ў API дадзеных фрэймворка.
Обработка адправлення дадзеных з useActionState
useActionState заменяе ручнае кераванне статусамі isSubmitting, error і result. Вы пасуеце дзеянне і пачатковы статус; вы атрымліваете тэчны статус, дзеянне, якое будзе выкарыстоўвацца для формы, і флаг пра чаканне. Дзеянне атрымлівае пыльны статус і FormData, а тое, што яно вяртае, становіцца наступным статусам.
import { useActionState } from "react";
Хутчэй submitFeedback пераканальвае паведамленне, зберагае яго і вяртае або пазнаку пра адзінак, або пазнаку пра успех, якія форма адразу відобразжае.
async function submitFeedback(prevState, formData) {
const message = formData.get("message");
if (message.length < 5) {
return { error: "Message is too short." };
}
await saveFeedback(message);
return { success: true };
}function FeedbackForm() {
const [state, formAction, isPending] = useActionState(submitFeedback, {}); return (
<form action={formAction}>
<textarea name="message" required />
<button disabled={isPending}>
{isPending ? "Sending…" : "Send feedback"}
</button>
{state.error && <p className="error">{state.error}</p>}
{state.success && <p>Thanks for the feedback!</p>}
</form>
);
}
Пакалькі ўздзеянне адбываецца через натыўны атрыбут action формы, таму гэта падтрымлівае прагрэсіўнае паўнэчванне. Згадайце, што адправка даных да сервера раней, чым будзе завершана іх обработка, працюе толькі тады, калі уздзеянне ўжо є функцыяй сервера; звычная кліентская функцыя вымагае завантажэння JavaScript. Кроме тыго, formData.get вяртае null для недаступнага поля, таму ў рэальным кодзе трэба гэта захістваць.
Чытанне статусу формы з будзь-каго падчыненага элемента за дапамогою useFormStatus
Кнопка адправкі, якая знаходзится глыбока ўнутры формы, часта патрэбуе ведаць, чы розпачата адправка даных, і передаванне значэння isPending через кожны слой є клопачлівым. useFormStatus, імпортаваны з react-dom, чытае статус найбліжэйшага родніка <form>.
import { useFormStatus } from "react-dom";
SubmitButton не прыёмляе жадных параметраў; ён бера стан упэўнення з асупрацоўванага яму формы, што робіць яго ідеяльным для спакульнаваных компонентаў кантролкаў.
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? "Saving…" : "Save"}</button>;
}function SettingsForm({ action }) {
return (
<form action={action}>
<input name="username" />
<SubmitButton />
</form>
);
}
Хук неабходна вызваць у компоненте, які асупрацоўваны ўнутрь <form>. Яго вызов у самом компоненте, які асупрацоўваны форму, не вяртае стану упэўнення.
Раннее адображэнне запланаваных рэзультатаў за дапамогою useOptimistic
useOptimistic адразу паказвае прыбліжны рэзультат дзеяння пад час выканання рэальнага запиту.
import { useOptimistic, useRef } from "react";
У TodoList хук вылучае optimisticTodos з параметра todos, дадаючы элементы, пазначаныя як pending: true. Дзеяння формы дадае оптымістичны элемент, скасоўвае форму чераз ref, а потым чакае на рэальнае дзеяння сервера. Элементы упэўнення адображаюцца з павулкай прозрачнасці.
function TodoList({ todos, addTodoAction }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newTodo) => [...state, { ...newTodo, pending: true }]
);
const formRef = useRef(null); async function handleSubmit(formData) {
const title = formData.get("title");
addOptimisticTodo({ id: Date.now(), title });
formRef.current?.reset();
await addTodoAction(formData);
} return (
<div>
<form ref={formRef} action={handleSubmit}>
<input name="title" required />
<button type="submit">Add</button>
</form>
<ul>
{optimisticTodos.map((todo) => (
<li key={todo.id} style={{ opacity: todo.pending ? 0.5 : 1 }}>
{todo.title}
</li>
))}
</ul>
</div>
);
}
Ментальная модель ўсё гэта дастоўна: оптымістычны стан існуе толькі пакуль выпалюецца дзеянне. Калі яно завершыцца, React яго адхоўчае і паказвае тое, што зараз знаходзіцца ў todos. У разе успеху родны компонент должен запростаць апошнюю версію todos (на прыклад, праз перапрацоўку), інакш айтэм зникае; у разе неудачы ён автаматычна зникае, гэта і є адката. Пра тыя паведамленні пра аберанне таксама трэба пазаботыцца. Статыя на useOptimistic rollback failure modes рассказвае пра крайнія случаў.
Бонус: ref як звычайны проп
У React 19 функцыйныя компоненты таксама можу прыміць ref як звычайны проп, таму forwardRef у большасці случаў не патрэбен. Це дапамагае усунуць вельмі большую частку дапаможных элементаў у бібліятэках компонентав.
Ключовыя выводы
use()распаковвае прамісы і контэкст пад адрадзенне з вядомасю Suspense, але праміс должен быць стабільным.useActionStateаб’еднвае стан, рэзультат і флаг чакання ў адні хук, базаваны на форме.useFormStatusчытае статус абмежваючай формы з ўсіроднага элемента, усуноўчы неабходнасць праходжання праз розныя элементы.useOptimisticпаказвае апэктываныя рэзультаты пад час выкарыстоўвання дзеяння і спакоюецца на свежых прапах для ўтримання іх.- Перад перзначэннем камбінацыі
useStateіuseEffectпераканайцеся, чы не падходзіць адны з гэтых варыянтаў.