Хуки 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’а, пагинации, веб-воркеров и т. д.).Как вызвать метод дочернего компонента из родительского компонента с помощью useImperativeHandle
2 года назад·3 мин. на чтение
Быстрый старт с useImperativeHandle
В этой статье будет показано, как вызвать метод дочернего компонента с помощью ссылки. Чтобы решить эту проблему, мы будем использовать хуки
Хук
Теперь давайте сосредоточимся на нашей задаче. Мы хотим вызвать метод (
На этом этапе мы можем создать ссылку в родительском компоненте с помощью хука
В родительский компонент нам нужно импортировать этот
useRef
и useImperativeHandle
.
Дочерний компонент
Начнем с простого дочернего компонента, в котором содержится кнопка. Нажатие на кнопку вызывает внутренний методdoSomething
.
// Child.jsx function Child(props, ref) { const doSomething = () => { console.log("do something"); }; return ( <div> <h1>Child Component</h1> <button onClick={doSomething}>Run</button> </div> ); } export default Child;
Родительский компонент
Далее рассмотрим родительский компонент. В нем используется дочерний компонент, описанный выше. Обратите внимание, что в родительском компоненте есть собственная кнопка сохранения.// App.jsx import Child from "./Child"; function App() { const save = () => {}; return ( <div> <Child /> <button onClick={save}>Save</button> </div> ); } export default App;
Хук useImperativeHandle
Теперь давайте сосредоточимся на нашей задаче. Мы хотим вызвать метод (doSomething
) дочернего компонента при нажатии кнопки (Save
) из родительского компонента.
Чтобы вызвать метод из дочернего компонента, нам нужно сначала выставить его наружу.
useImperativeHandle
определяет значение объекта, которое предоставляется родительскому компоненту при использовании ref
. Добавляя наш метод к этому объекту, мы делаем его доступным в родительских компонентах.
// Child.jsx import { useImperativeHandle } from "react"; function Child(props, ref) { const doSomething = () => { console.log("do something"); }; useImperativeHandle(ref, () => ({ doSomething })); return ( <div> <h1>Child Component</h1> <button onClick={doSomething}>Run</button> </div> ); } export default Child;
useImperativeHandle
следует использовать с forwardRef
.
forwardRef
позволяет родительскому компоненту передавать ссылки своим дочерним элементам. Чтобы прикрепить функции или поля к этой ссылке (к рефу), используется хук useImperativeHandle
.
// Child.jsx import { forwardRef, useImperativeHandle } from "react"; function Child(props, ref) { const doSomething = () => { console.log("do something"); }; useImperativeHandle(ref, () => ({ doSomething })); return ( <div> <h1>Child Component</h1> <button onClick={doSomething}>Run</button> </div> ); } export default forwardRef(Child); // Child обернут в forwardRef
useRef
и передать ее дочернему компоненту. Получив эту ссылку, мы можем вызвать метод doSomething
дочернего компонента.
// App.jsx import { useRef } from "react"; import Child from "./Child"; function App() { const childRef = useRef(null); const save = () => { if (childRef.current) { childRef.current.doSomething(); } }; return ( <div> <Child ref={childRef} /> <button onClick={save}>Save</button> </div> ); } export default App;
Добавим TypeScript
Далее посмотрим, какие изменения нужно сделать, чтобы вызвать тот же дочерний метод из родительского компонента при использовании TypeScript. Во-первых, нам нужно определить новый интерфейс, содержащий метод, который будет представлен.Затем новый тип (export interface RefType { doSomething: () => void; }
RefType
) используется при получении ссылки в дочернем компоненте.
Ниже приведен полный код дочернего компонента.function Child(props: PropsType, ref: Ref<RefType>)
// Child.jsx import { forwardRef, useImperativeHandle, Ref } from "react"; export interface PropsType {} export interface RefType { doSomething: () => void; } function Child(props: PropsType, ref: Ref<RefType>) { const doSomething = () => { console.log("do something"); }; useImperativeHandle(ref, () => ({ doSomething })); return ( <div> <h1>Child Component</h1> <button onClick={doSomething}>Run</button> </div> ); } export default forwardRef(Child);
RefType
, содержащий все публичные дочерние методы, и использовать его при создании ref
.
Полный код родительского компонента.// App.jsx import Child, { RefType } from "./Child"; //... const childRef = useRef<RefType>(null);
import { useRef } from "react"; import Child, { RefType } from "./Child"; function App() { const childRef = useRef<RefType>(null); const save = () => { if (childRef.current) { childRef.current.doSomething(); } }; return ( <div> <Child ref={childRef} /> <button onClick={save}>Save</button> </div> ); } export default App;