Хуки 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’а, пагинации, веб-воркеров и т. д.).Хорошие практики и шаблоны проектирования для Vue Composables
год назад·1 мин. на чтение
Composables (composition API) можно использовать для хранения основной бизнес-логики (например, вычислений, действий, процессов), поэтому они являются важной частью приложения. К сожалению, в командах не всегда есть соглашения для написания composables.
Эти соглашения важны для того, чтобы компоненты были удобными в обслуживании, легко тестируемыми и действительно полезными.
Общие шаблоны проектирования
На мой взгляд, лучшим источником информации о шаблонах для создания composables на самом деле является документация Vue.js, с которой вы можете ознакомиться здесь.Базовый composable
В документации Vue показан следующий пример компонуемогоuseMouse
:
Позже это можно использовать в компоненте следующим образом:// mouse.js import { ref, onMounted, onUnmounted } from 'vue' // по соглашению имена composable функций начинаются с "use" export function useMouse() { // состояние инкапсулировано и управляется самим composable const x = ref(0) const y = ref(0) // composable может обновлять состоянием function update(event) { x.value = event.pageX y.value = event.pageY } // composable также может иметь доступ к жизненному циклу родительского компонента // для установки и очистки эффектов onMounted(() => window.addEventListener('mousemove', update)) onUnmounted(() => window.removeEventListener('mousemove', update)) // отдаем состояние наружу как возвращаемое значение return { x, y } }
<script setup> import { useMouse } from './mouse.js' const { x, y } = useMouse() </script> <template>Mouse position is at: {{ x }}, {{ y }}</template>
Асинхронные composables
Для получения данных Vue рекомендует следующую компонуемую структуру:Затем это можно использовать в компоненте следующим образом:import { ref, watchEffect, toValue } from 'vue' export function useFetch(url) { const data = ref(null) const error = ref(null) watchEffect(() => { // обнуляем состояние перед запросом data.value = null error.value = null // toValue() разворачивает потенциальные рефы или геттеры fetch(toValue(url)) .then((res) => res.json()) .then((json) => (data.value = json)) .catch((err) => (error.value = err)) }) return { data, error } }
<script setup> import { useFetch } from './fetch.js' const { data, error } = useFetch('...') </script>
Соглашения composable
Основываясь на приведенных выше примерах, вот контракт, которому должны следовать все composables:- Имена composable файлов должны начинаться с
use
, напримерuseSomeAmazingFeature.ts
- Он может принимать входные аргументы, которые могут быть примитивными типами, такими как строки, или может принимать ссылки и геттеры, но для этого требуется использовать вспомогательное средство
toValue
- Composable должен возвращать значение
ref
, к которому можно получить доступ после деструктурирования composable, напримерconst { x, y } = useMouse()
- Composables могут содержать глобальное состояние, к которому можно получить доступ и изменить в приложении.
- Composable может вызвать побочные эффекты, такие как добавление прослушивателей событий окна, но их следует очищать при отключении компонента.
- Composables следует вызывать только в
<script setup>
или в хукеsetup()
. В этих контекстах их также следует называть синхронно. В некоторых случаях их также можно вызывать в обработчиках жизненного цикла, таких какonMounted()
- Composables могут вызывать другие composables внутри
- Composables должны заключать в себе определенную логику, а когда они слишком сложны, их следует извлекать в отдельные composables для облегчения тестирования.