Пошук уроків, статей та іншого контенту
Застосуємо useTransition для позначення неп термінових оновлень і збереження чутливого інтерфейсу швидким.
useTransitionУ React усі оновлення стану зазвичай мають однаковий пріоритет. Якщо після зміни стану потрібно відрендерити великий список або складне дерево компонентів, браузер може на короткий час перестати реагувати на введення користувача.
useTransition дає змогу позначити частину оновлень як не термінові. React спочатку обробить важливі для взаємодії оновлення, наприклад введення тексту або натискання кнопки, а потім виконає перехідний рендер.
Хук повертає два значення:
const [isPending, startTransition] = useTransition();startTransition — функція, у яку потрібно передати оновлення стану з низьким пріоритетом;
isPending — true, поки React виконує перехідний рендер.
Використання має такий вигляд:
startTransition(() => {
setState(nextValue);
});Уявімо поле пошуку з великим списком результатів:
Значення текстового поля має оновлюватися негайно.
Фільтрація та відображення великого списку можуть виконуватися пізніше.
Якщо позначити обидва оновлення як перехідні, саме поле може почати реагувати із затримкою. Тому стан, який керує введенням, потрібно оновлювати звичайним способом, а стан фільтрації — через startTransition.
import { useState, useTransition } from "react";
const products = Array.from({ length: 50000 }, (_, index) => ({
id: index + 1,
name: `Product ${index + 1}`,
category: index % 2 === 0 ? "Electronics" : "Books",
}));
export default function ProductSearch() {
const [inputValue, setInputValue] = useState("");
const [searchQuery, setSearchQuery] = useState("");
const [isPending, startTransition] = useTransition();
const visibleProducts = products.filter((product) =>
product.name.toLowerCase().includes(searchQuery.toLowerCase()),
);
function handleChange(event) {
const nextValue = event.target.value;
// Термінове оновлення: поле має реагувати без затримки.
setInputValue(nextValue);
// Нетермінове оновлення: список можна перемалювати пізніше.
startTransition(() => {
setSearchQuery(nextValue);
});
}
return (
<main>
<h1>Пошук товарів</h1>
<label>
Пошуковий запит
<input
type="search"
value={inputValue}
onChange={handleChange}
placeholder="Введіть назву товару"
/>
</label>
{isPending && <p aria-live="polite">Оновлюємо результати…</p>}
<p>
Знайдено товарів: {visibleProducts.length}
</p>
<ul>
{visibleProducts.map((product) => (
<li key={product.id}>
{product.name} — {product.category}
</li>
))}
</ul>
</main>
);
}У цьому прикладі:
inputValue оновлюється негайно;
searchQuery оновлюється в межах transition;
visibleProducts перераховується на основі searchQuery;
isPending показує користувачу, що новий список ще формується.
React може перервати перехідний рендер, якщо користувач продовжить вводити текст. Тоді React відкине застарілу роботу та спробує відрендерити результати вже для актуального запиту.
Виклик startTransition не створює окремий потік і не запускає JavaScript паралельно. JavaScript-код усе одно виконується в головному потоці браузера.
Transition повідомляє React:
Це оновлення можна перервати та виконати після більш важливих оновлень.
Це особливо корисно, коли оновлення спричиняє:
зміну великого списку;
перемикання складного інтерфейсу;
рендеринг важкого дерева компонентів;
зміну фільтра або сортування;
перемикання вкладки зі значним обсягом вмісту.
useTransition не прискорює самі обчислення. Якщо окрема синхронна функція виконується дуже довго, вона все одно блокуватиме головний потік. Хук лише допомагає React краще керувати порядком і перериванням рендерингу.
isPendingisPending можна використати для індикації стану переходу:
function Tabs() {
const [activeTab, setActiveTab] = useState("overview");
const [isPending, startTransition] = useTransition();
function selectTab(tab) {
startTransition(() => {
setActiveTab(tab);
});
}
return (
<section>
<div>
<button
type="button"
onClick={() => selectTab("overview")}
disabled={isPending}
>
Огляд
</button>
<button
type="button"
onClick={() => selectTab("analytics")}
disabled={isPending}
>
Аналітика
</button>
</div>
{isPending && <p>Завантаження вкладки…</p>}
{activeTab === "overview" && <Overview />}
{activeTab === "analytics" && <Analytics />}
</section>
);
}
function Overview() {
return <p>Загальна інформація</p>;
}
function Analytics() {
return <p>Складний компонент аналітики</p>;
}Індикація має бути ненав'язливою. Зазвичай достатньо:
показати текст «Оновлення…»;
додати індикатор завантаження;
тимчасово змінити вигляд кнопки;
залишити попередній контент на екрані, доки новий не буде готовий.
useTransition і керовані поляКерований текстовий input не слід оновлювати лише в transition:
function IncorrectSearch() {
const [value, setValue] = useState("");
const [isPending, startTransition] = useTransition();
function handleChange(event) {
startTransition(() => {
setValue(event.target.value);
});
}
return <input value={value} onChange={handleChange} />;
}Це неправильний підхід, тому що значення керованого поля має бути синхронним із дією користувача. Transition може відкласти оновлення, через що поле реагуватиме із затримкою.
Правильніше розділити стани:
function CorrectSearch() {
const [inputValue, setInputValue] = useState("");
const [query, setQuery] = useState("");
const [isPending, startTransition] = useTransition();
function handleChange(event) {
const nextValue = event.target.value;
setInputValue(nextValue);
startTransition(() => {
setQuery(nextValue);
});
}
return (
<div>
<input value={inputValue} onChange={handleChange} />
<p>Пошук: {query}</p>
{isPending && <span>Оновлення…</span>}
</div>
);
}Тут поле використовує терміновий стан inputValue, а важкий контент може залежати від нетермінового query.
useTransition не потрібенНе кожне оновлення стану потрібно обгортати в startTransition. Не варто використовувати його:
для простих оновлень тексту;
для відкриття маленького меню;
для перемикання локального прапорця;
для стану керованого поля введення;
лише заради самого факту використання concurrent API.
Transition має сенс тоді, коли оновлення справді може бути важким і не повинно блокувати безпосередню взаємодію з інтерфейсом.
startTransitionReact визначає пріоритет лише для оновлень стану, виконаних у переданій функції:
startTransition(() => {
setView("details");
});Сам виклик іншої функції без оновлення стану не змінює пріоритет:
startTransition(() => {
calculateData();
});Transition може бути перерваний під час рендерингу, але це не є заміною скасуванню мережевих запитів або очищенню ефектів. Побічні ефекти потрібно виконувати відповідно до їхнього життєвого циклу та окремо обробляти їхнє скасування.
isPending не означає завершення всіх асинхронних операційisPending описує стан React transition. Він не є універсальним індикатором для всіх мережевих запитів чи асинхронних задач.
Якщо перехід пов'язаний із завантаженням даних, стан запиту потрібно узгодити з механізмом отримання даних, який використовується в застосунку.
Якщо обгорнути в startTransition і поле введення, і важкий список, користувач може відчути затримку самого введення.
Розділяйте стани на:
термінальний стан взаємодії;
нетермінальний стан важкого представлення даних.
useTransition прискорить обчисленняХук не оптимізує алгоритми фільтрації та не зменшує кількість елементів у DOM. Він лише дає React можливість відкласти або перервати рендеринг.
Якщо список надто великий, окремо можуть знадобитися оптимізація обчислень, мемоізація або віртуалізація списку. Це інші оптимізаційні підходи.
isPending для приховування всього інтерфейсуПовне приховування поточного контенту під час кожного transition може погіршити сприйняття інтерфейсу. Часто краще залишити попередні дані на екрані та показати невеликий індикатор оновлення.
// Оновлення вже виконано до startTransition.
setActiveTab("analytics");
startTransition(() => {
// Тут немає оновлення, яке можна позначити як transition.
});Потрібне оновлення має бути безпосередньо всередині callback:
startTransition(() => {
setActiveTab("analytics");
});useTransition використовується для позначення нетермінових оновлень інтерфейсу.
startTransition приймає функцію з оновленнями стану низького пріоритету.
isPending дає змогу показати стан виконання transition.
Термінальні оновлення, зокрема значення керованого input, не слід робити transition-оновленнями.
Transition може бути перерваний React, якщо з'явиться новіша взаємодія користувача.
useTransition не прискорює важкі обчислення, а допомагає не блокувати пріоритетні оновлення інтерфейсу.