Гид по технологиям

Что нового в React 18 — useTransition, useDeferredValue, useSyncExternalStore, useInsertionEffect и useId

6 min read React Обновлено 15 Apr 2026
React 18: новые хуки для производительности
React 18: новые хуки для производительности

Important: все примеры предполагают React 18+ и соответствующую сборку. При миграции проверяйте совместимость библиотек и тестируйте на реальных сценариях.

Логотип React

В марте 2022 года команда React официально выпустила React 18. В центре релиза — концепция «конкурентного рендеринга» (concurrent rendering), идея которой — сделать процесс обновления DOM прерываемым и управляемым по приоритетам. Это позволяет улучшать отзывчивость интерфейса в сложных приложениях.

Ниже мы разберём основные новые хуки: useTransition, useDeferredValue, useSyncExternalStore, useInsertionEffect и useId — как они работают, где помогают, где не помогают, и как безопасно внедрять их в проект.

Коротко о concurrent rendering

Concurrent rendering — режим, в котором React может приостанавливать и возобновлять работу по рендерингу, чтобы отдавать приоритет срочным обновлениям (например, вводу пользователя) перед менее срочными (например, тяжёлыми вычислениями списка). Это не всегда означает асинхронный код в вашем приложении — это модель приоритизации внутри React.

Понятие: concurrent rendering — модель рендеринга, позволяющая React прерывать и возобновлять обновления по приоритетам.

useTransition — понижение приоритета обновления

useTransition позволяет пометить обновления состояния как «несрочные», чтобы срочные обновления могли прервать их. Типичный сценарий — ввод в поле поиска (срочно) и одновременное обновление тяжёлого списка результатов (несрочно).

Пример компонента SearchPage, имитирующий поиск и обновляющий два состояния — поле ввода и большой список результатов:

import { useState } from "react";  
  
function SearchPage() {  
    const [input, setInput] = useState("")  
    const [list, setList] = useState([]);  
  
    const listSize = 30000  
   
    function handleChange(e) {  
        setInput(e.target.value);  
        const listItems = [];  
  
        for (let i = 0; i < listSize; i++){  
            listItems.push(e.target.value);  
        }  
  
        setList(listItems);  
    }  
  
    return (  
      
{list.map((item, index) => { return {item}
})}
); } export default SearchPage;

Если вводить символы быстро, следует заметить задержку: обновление ввода и списка конкурируют за ресурсы, из-за чего ввод может «лагать».

Обновим компонент с использованием useTransition:

import {useState, useTransition} from "react";  
  
function SearchPage() {  
    const [isPending, startTransition] = useTransition();  
    const [input, setInput] = useState("")  
    const [list, setList] = useState([]);  
  
    const listSize = 30000  
  
    function handleChange(e) {  
        setInput(e.target.value);  
  
        startTransition(() => {  
            const listItems = [];  
  
            for (let i = 0; i < listSize; i++){  
                listItems.push(e.target.value);  
            }  
  
            setList(listItems);  
        });  
    }  
  
    return (  
      
{isPending ? "...Loading results" : list.map((item, index) => { return {item}
})}
); } export default SearchPage;

Что меняется: ввод становится немедленным, а обновление списка выполняется с пониженным приоритетом. Флаг isPending указывает на текущую «переходную» операцию.

Совет: используйте startTransition вокруг тяжёлых вычислений или setState, которые могут быть отложены, чтобы улучшить отзывчивость UI.

useDeferredValue — удержание старого значения во время обновления

useDeferredValue позволяет компоненту временно «останавливать» значение и показывать старое значение, пока новый рендер не завершится. Это удобно, когда необязательно сразу показывать изменения в тяжёлом списке, но важно, чтобы ввод пользователя был отзывчивым.

Пример с использованием useDeferredValue:

import { useState, useTransition, useDeferredValue } from "react";  
  
function SearchPage() {  
  
    const [,startTransition] = useTransition();  
    const [input, setInput] = useState("")  
    const [list, setList] = useState([]);  
  
    const listSize = 30000  
    
    function handleChange(e) {  
        setInput(e.target.value);  
  
        startTransition(() => {  
            const listItems = [];  
  
            for (let i = 0; i < listSize; i++){  
                listItems.push(e.target.value);  
            }  
  
            setList(listItems);  
        });  
    }  
  
    const deferredValue = useDeferredValue(input);  
  
    return (  
      
{list.map((item, index) => { return {item}
})}
); } export default SearchPage;

Отличие от useTransition: useDeferredValue возвращает «отложенное» значение (deferredValue), которое остаётся прежним до завершения перехода, а интерфейс может продолжать обрабатывать ввод.

useSyncExternalStore — надёжная подписка на внешние сторы

useSyncExternalStore предназначен для библиотек и позволяет подписываться на внешние хранилища (сторы) и безопасно получать их снимок состояния как для клиентского, так и для серверного рендеринга.

Сигнатура:

const state = useSyncExternalStore(subscribe, getSnapshot[, getServerSnapshot]);

Параметры:

  • state: возвращаемое текущее значение стора;
  • subscribe: функция регистрации колбэка при изменении стора;
  • getSnapshot: функция, возвращающая текущее значение стора;
  • getServerSnapshot: (опционально) снимок для серверного рендеринга.

useSyncExternalStore упрощает поддержку консистентного поведения при SSR и гидрации, снижая риск рассинхронизации состояния.

useInsertionEffect — оптимизация для CSS-in-JS

useInsertionEffect — хук для библиотек, добавляющих стили в DOM (CSS-in-JS). Он позволяет вставлять стили в DOM перед тем, как React будет читать и измерять макет (useLayoutEffect), что помогает избежать мерцаний и проблем с порядком подключения стилей.

Используйте этот хук в коде библиотек, которые создают теги