Как работает useTransition и useDeferredValue в React?

SeniorReact · Frontend·Обновлено 22 июля 2026
Коротко
useTransition позволяет пометить обновление состояния как некритичное, откладывая его рендер, а useDeferredValue откладывает обновление конкретного значения — оба хука используют механизм Concurrent Rendering для приоритизации UI-отзывчивости.

useTransition и useDeferredValue в React

Оба хука появились в React 18 как часть модели Concurrent Rendering. Они позволяют React прерывать «тяжёлые» рендеры в пользу срочных обновлений (например, ввод пользователя), делая интерфейс отзывчивым.

useTransition

useTransition возвращает кортеж [isPending, startTransition]. Любое обновление состояния, обёрнутое в startTransition, React считает некритичным и может отложить или прервать, если появится более приоритетное обновление.

import { useState, useTransition } from 'react';

function SearchPage() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState<string[]>([]);
  const [isPending, startTransition] = useTransition();

  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    // Срочное обновление — поле ввода реагирует мгновенно
    setQuery(e.target.value);

    // Некритичное обновление — список результатов можно отложить
    startTransition(() => {
      setResults(heavyFilter(e.target.value));
    });
  }

  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending ? <span>Загрузка...</span> : <ResultList items={results} />}
    </>
  );
}

isPending — флаг, который true пока отложенный рендер ещё не завершился. Это позволяет показывать индикатор загрузки без блокировки ввода.

useDeferredValue

useDeferredValue принимает значение и возвращает его «отложенную» копию. React рендерит компонент сначала со старым значением (мгновенно), а потом фоново пересчитывает с новым.

import { useState, useDeferredValue, memo } from 'react';

const HeavyList = memo(function HeavyList({ query }: { query: string }) {
  // Дорогой рендер — тысячи элементов
  const items = heavyFilter(query);
  return <ul>{items.map(i => <li key={i}>{i}</li>)}</ul>;
});

function SearchPage() {
  const [query, setQuery] = useState('');
  // deferredQuery обновляется позже, чем query
  const deferredQuery = useDeferredValue(query);

  return (
    <>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      {/* Компонент получит старое значение, пока фон не пересчитает */}
      <HeavyList query={deferredQuery} />
    </>
  );
}

Ключевые различия

useTransition useDeferredValue
Управляет вызовом setState конкретным значением
Есть isPending да нет (нужен сравнивать value с deferredValue)
Когда использовать когда обновляете состояние сами когда получаете значение из props/контекста

Важные нюансы

  • Оба хука работают только в Concurrent Mode (React 18+, без ReactDOM.render).
  • startTransition не откладывает сетевые запросы — только рендер. Для серверных данных нужен use + Suspense или специальные хуки.
  • useDeferredValue эффективен только вместе с memo — иначе отложенный рендер всё равно пересчитывается синхронно.
  • Если машина пользователя мощная, разницы может не быть заметно — React адаптируется к скорости устройства.
  • Нельзя читать isPending внутри startTransition — он становится true асинхронно после вызова.

Что хочет услышать интервьюер

Понимание модели Concurrent Rendering и того, что React может прерывать рендеры

Чёткое разграничение сценариев: useTransition — когда управляешь setState сам, useDeferredValue — когда значение приходит снаружи

Знание флага isPending и его практического применения для UX

Понимание, что useDeferredValue требует memo для реального эффекта

Осознание ограничений: хуки не откладывают сетевые запросы, работают только в Concurrent Mode

Пример: useTransition — откладываем тяжёлую фильтрацию при вводе

import { useState, useTransition } from 'react';

function heavyFilter(q: string): string[] {
  // Имитация дорогой операции
  return Array.from({ length: 10_000 }, (_, i) => `item-${i}`)
    .filter(s => s.includes(q));
}

export function SearchWithTransition() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState<string[]>([]);
  const [isPending, startTransition] = useTransition();

  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    // Ввод обновляется мгновенно
    setQuery(e.target.value);

    // Список обновляется с низким приоритетом
    startTransition(() => {
      setResults(heavyFilter(e.target.value));
    });
  }

  return (
    <div>
      <input value={query} onChange={handleChange} placeholder="Поиск..." />
      {isPending && <p>Обновление списка...</p>}
      <ul>
        {results.slice(0, 20).map(r => <li key={r}>{r}</li>)}
      </ul>
    </div>
  );
}

Пример: useDeferredValue — откладываем props в дочернем компоненте

import { useState, useDeferredValue, memo } from 'react';

interface ListProps {
  query: string;
}

// memo обязателен — без него отложенный рендер не даст эффекта
const HeavyList = memo(function HeavyList({ query }: ListProps) {
  const items = Array.from({ length: 5_000 }, (_, i) => `result-${i}`)
    .filter(s => s.includes(query));

  return (
    <ul>
      {items.slice(0, 50).map(item => (
        <li key={item}>{item}</li>
      ))}
    </ul>
  );
});

export function SearchWithDeferred() {
  const [query, setQuery] = useState('');
  // deferredQuery «отстаёт» от query при быстром вводе
  const deferredQuery = useDeferredValue(query);

  // Показываем, что список ещё не актуален
  const isStale = query !== deferredQuery;

  return (
    <div>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="Поиск..."
      />
      <div style={{ opacity: isStale ? 0.5 : 1 }}>
        <HeavyList query={deferredQuery} />
      </div>
    </div>
  );
}

Типичные ошибки

Считают, что startTransition откладывает fetch-запросы — он откладывает только рендер

Используют useDeferredValue без memo, не получают никакого эффекта

Путают useTransition с debounce/throttle — это разные механизмы с разной семантикой

Не проверяют isPending и не показывают пользователю индикатор ожидания, ухудшая UX

Пытаются использовать хуки в React 17 или в legacy-режиме ReactDOM.render

Лучшие курсы по теме

изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Feature-Sliced Design

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.5
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее