Хуки useTransition и useDeferredValue в ReactJS 18
2 года назад·3 мин. на чтение
В React 18, релиз которого произошел в марте 2022, появилось много новых инструментов для написания производительных и отзывчивых приложений. Одним из заметных изменений является механизм рендеринга с новой ключевой концепцией: конкурентный рендеринг (concurrent rendering).
В этой статье повнимательнее рассмотрим два новых хука:
Какое обновление можно считать срочным, а какое обычным?
Хук
До React 18 все обновления состояния помечались как "срочные". Это означает, что все обновления состояния обрабатывались одинаково с одинаковым приоритетом.
С помощью
Когда использовать
Одним из примеров может быть список товаров с параметрами фильтрации.
Когда вы переключаете чекбоксы, чтобы выбрать размер или цвет одежды, вы ожидаете, что чекбоксы сразу же отобразят отмеченное или снятое состояние.
А сам список товаров, которые необходимо обновить согласно фильтрам, может быть отдельным и менее срочным обновлением.
Как использовать
Хук
Когда использовать
С помощью
Как использовать
useTransition()
и useDeferredValue()
.
Эти два хука дают возможность определять приоритет обновления состояния, или, скорее, указывать, является ли обновление менее важным, чем другие, и откладывать его в пользу более срочных.
- Срочные обновления: отражают прямое взаимодействие, такое как набор текста, клики, нажатия и т. д., т.е. то с чем взаимодействует пользователь. Когда вы вводите текст в поле ввода, вы хотите сразу увидеть введенный вами текст. В противном случае UI будет казаться медленным и подлагивать. Поэтому мы хотим сделать такие обновления приоритетным.
- Обычные обновления: переход пользовательского интерфейса из одного вида в другой. Пользователи знают, что представление должно измениться или обновиться (например, когда ожидается ответ на запрос данных). Даже если есть небольшая задержка, это можно рассматривать как ожидаемое поведение, и это не будет восприниматься как медлительность приложения.
Хук useTransition()
и функция startTransition()
До React 18 все обновления состояния помечались как "срочные". Это означает, что все обновления состояния обрабатывались одинаково с одинаковым приоритетом.
С помощью useTransition()
теперь можно пометить некоторые обновления состояния как несрочные.
Когда использовать useTransition()
?
Одним из примеров может быть список товаров с параметрами фильтрации.
Когда вы переключаете чекбоксы, чтобы выбрать размер или цвет одежды, вы ожидаете, что чекбоксы сразу же отобразят отмеченное или снятое состояние.
А сам список товаров, которые необходимо обновить согласно фильтрам, может быть отдельным и менее срочным обновлением.
Как использовать useTransition()
?
function App() { const [isPending, startTransition] = useTransition(); const [searchQuery, setSearchQuery] = useState(''); // запрос данных, который занимает некоторое время const filteredResults = getProducts(searchQuery); function handleQueryChange(event) { startTransition(() => { // оборачивая setSearchQuery() в startTransition(), // мы помечаем эти обновления как менее важные setSearchQuery(event.target.value); }); } return ( <div> <input type="text" onChange={handleQueryChange} /> {isPending && <span>Loading...</span>} <ProductsList results={filteredResults} /> </div> ); }
Хук useDeferredValue()
useDeferredValue()
очень похож на useTransition()
в том, что он позволяет отложить несрочное обновление состояния, но применяется его к части дерева.
Это похоже методы debounce и throttle, которые мы часто используем для отложенных обновлений. React будет работать с такими обновлениями, как только завершатся срочные обновления.
Когда использовать useDeferredValue()
?
С помощью useTransition()
вы сами решаете, когда конкретное обновление состояния может быть помечено как менее срочное. Но иногда такой возможности может и не быть, например, если фрагмент кода находится в сторонней библиотеке.
В таких случаях можно воспользоваться хуком useDeferredValue()
. С помощью useDeferredValue()
вы можете обернуть значение и пометить его изменения как менее важные и, следовательно, отложить повторный рендеринг.
useDeferredValue()
будет возвращать предыдущее значение до тех пор, пока есть более срочные обновления для завершения и отображения дерева с обновленным значением.
Как использовать useDeferredValue()
?
function ProductsList({ results }) { // deferredResults получат обновленные данные // когда завершатся срочные обновления const deferredResults = useDeferredValue(results); return ( <ul> {deferredResults.map((product) => ( <li key={product.id}>{product.title}</li> ))} </ul> ); }
Итоги
Эти два новых хука позволяют сделать интерфейсы максимально отзывчивыми, даже в сложных приложениях с большим количеством повторных рендерингов, отдавая приоритет обновлениям, которые имеют решающее значение для взаимодействия с пользователем, и помечая некоторые другие как менее важные. Это не означает, что нужно оборачивать все состояния этими хуками. Их следует использовать в крайнем случае, если приложение или компоненты не могут быть оптимизированы другими способами (например, при помощи lazy loading’а, пагинации, веб-воркеров и т. д.).Кастомный React хук для изменения темы веб-приложения
2 года назад·2 мин. на чтение
Пишем кастомный React хук useTheme, который динамически меняет тему веб-приложения с помощью CSS переменных.
В этой статье напишем кастомный хук на ReactJS, который будет менять тему веб-приложения.
Особенности хука useTheme, который мы реализуем:
Код хука
Сам хук выглядит довольно просто.
Использование хука
Здесь добавлены две кнопки, каждая из которых отвечает за переключение на светлую или темную тему. Они обернуты в
- использует CSS переменные
- быстро кастомизируется
- плавный переход между темами
- умеет сохранять выбранную тему в local storage
Код хука useTheme
Сам хук выглядит довольно просто.
// hooks/use-theme.js import { useLayoutEffect, useState } from 'react' const isDarkTheme = window?.matchMedia('(prefers-color-scheme: dark)').matches const defaultTheme = isDarkTheme ? 'dark' : 'light' export const useTheme = () => { const [theme, setTheme] = useState( localStorage.getItem('app-theme') || defaultTheme ) useLayoutEffect(() => { document.documentElement.setAttribute('data-theme', theme) localStorage.setItem('app-theme', theme) }, [theme]) return { theme, setTheme } }
prefers-color-scheme
используется для определения выбранной пользователем темы (светлая или темная). Пользователь указывает свои предпочтения через настройку операционной системы или через настройку user agent.
document.documentElement.setAttribute('data-theme', theme)
- этой строчкой мы добавляем кастомный data-атрибут в тег html
. Таким образом, например, при выборе светлой темы в теге html
появится data-атрибут data-theme="light"
.
Добавление CSS переменных
Далее мы должны указать CSS переменные, которые будут иметь различные значение для разных тем. Например,--button-text-color
имеет значение #ffffff
при темной теме и #252525
- при светлой.
/* index.css */ html[data-theme='dark'] { --button-text-color: #ffffff; --button-background-color: #4e005c; --button-border-color: #ba8fc2; --background-color: #292929; --icon-color: #ba8fc2; } html[data-theme='light'] { --button-text-color: #252525; --button-background-color: #f9d4ff; --button-border-color: #4e005c; --background-color: #dfdfdf; --icon-color: #4e005c; }
Применение CSS переменных
Далее нам нужно применить эти CSS переменные к соответствующим элементам. Добавлениеtransition
дает плавность при переключении тем.
/* App.css */ .app__container { background-color: var(--background-color); transition: background-color 200ms linear; } .app__logo { color: var(--icon-color); transition: color 500ms linear; } .app__button, .app__button:hover, .app__button:focus, .app__button:active, .app__button:not(:disabled):not(.disabled):active { color: var(--button-text-color); background-color: var(--button-background-color); border-color: var(--button-border-color); transition: color 500ms linear, background-color 500ms linear, border-color 500ms linear; }
Использование хука useTheme
Здесь добавлены две кнопки, каждая из которых отвечает за переключение на светлую или темную тему. Они обернуты в ButtonGroup
.
Элементы логотип, контейнер и кнопка имеют соответствующие CSS классы, которые мы описали выше - app__logo
, app__container
и app__button
. В этих классах были использованы CSS переменные.
Исходный кодimport React from 'react' import Button from 'react-bootstrap/Button' import ButtonGroup from 'react-bootstrap/ButtonGroup' import { FaCoffee } from 'react-icons/fa' import { useTheme } from './hooks/use-theme' import './App.css' export default function App() { const { theme, setTheme } = useTheme() const handleLightThemeClick = () => { setTheme('light') } const handleDarkThemeClick = () => { setTheme('dark') } return ( <div className="app__container w-100 h-100 d-flex flex-column"> <div className="p-3 d-flex justify-content-end"> <ButtonGroup aria-label="Theme toggle"> <Button variant="secondary" onClick={handleLightThemeClick}> Light </Button> <Button variant="secondary" onClick={handleDarkThemeClick}> Dark </Button> </ButtonGroup> </div> <div className="flex-grow-1 d-flex flex-column justify-content-center align-items-center"> <FaCoffee size={100} className="app__logo mb-5" /> <div className="d-flex"> <Button className="app__button" type="button"> Subscribe </Button> </div> </div> </div> ) }