useRef
useRef
– хук в React, позволяющий хранить ссылку на значение, которое не нужно рендерить.
const ref = useRef(initialValue)
Справочник
useRef(initialValue)
Вызовите useRef
на верхнем уровне компонента, чтобы объявить один или несколько рефов:
import { useRef } from 'react';
function MyComponent() {
const intervalRef = useRef(0);
const inputRef = useRef(null);
// ...
Параметры
initialValue
: Изначальное значение, которое будет присвоено свойствуcurrent
при первом рендере. Оно может быть любого типа. При всех последующих рендерах значение этого аргумента будет игнорироваться.
Возвращаемое значение
useRef
возвращает объект с одним единственным свойством:
current
: Изначально оно равноinitialValue
. В дальнейшем ему можно присвоить другое значение. Если передать созданный при помощиuseRef
объект в качестве атрибутаref
любому JSX-узлу, React автоматически установит свойствоcurrent
.
При всех последующих рендерах useRef
будет возвращать один и тот же объект.
Предостережения
- В отличие от состояния свойство
ref.current
можно изменять напрямую. Однако если в нём хранится объект, использующийся для рендера (например, часть состояния), тогда этот объект изменять не стоит. - При изменении свойства
ref.current
React не ререндерит компонент. Поскольку реф это простой JavaScript-объект, React ничего не знает о его изменениях. - Не стоит перезаписывать или считывать
ref.current
во время рендера (за исключением первоначального). Это может привести к непредсказуемому поведению компонента. - В строгом режиме React вызовет функцию компонента дважды, чтобы помочь обнаружить возможные побочные эффекты. Такое поведение существует только в режиме разработки и никак не проявляется в продакшене. Каждый реф будет создан дважды, но одна из версий будет отброшена. Если компонент является чистой функцией (какой он и должен быть), это никак не скажется на его поведении.
Использование
Хранение ссылки на значение при помощи рефов
Вызовите useRef
на верхнем уровне компонента, чтобы объявить реф:
import { useRef } from 'react';
function Stopwatch() {
const intervalRef = useRef(0);
// ...
useRef
возвращает объект с одним единственным свойством current
, которое изначально равно переданному в useRef
значению.
При последующих рендерах useRef
будет возвращать один и тот же объект, чьё свойство current
можно считывать и перезаписывать. Это похоже на состояние, однако между состоянием и рефом существует одно важное отличие.
Изменение рефа не вызывает ререндер. Таким образом, рефы идеально подходят для хранения информации, которая не оказывает никакого влияния на визуальную составляющую компонента (например, в реф можно положить intervalId
). Чтобы обновить значение внутри рефа, нужно вручную изменить его свойство current
:
function handleStartClick() {
const intervalId = setInterval(() => {
// ...
}, 1000);
intervalRef.current = intervalId;
}
В дальнейшем этот intervalId
можно будет считать и использовать для очистки интервала:
function handleStopClick() {
const intervalId = intervalRef.current;
clearInterval(intervalId);
}
Используя рефы, можно быть уверенными в том, что:
- Информация хранится между ререндерами (в отличие от обычных переменных, которые сбрасываются при каждом ререндере).
- Изменение рефа не вызывает ререндер (в отличие от состояния, изменение которого вызывает ререндер).
- Информация является локальной для каждой копии компонента (в отличие от внешних переменных, которые являются общими для всех).
Поскольку изменения рефов не вызывают ререндер, они не подходят для хранения информации, которую нужно отображать на экране. Для этого лучше использовать состояние.
Подробнее о выборе между useRef
и useState
.
Example 1 of 2: Счётчик нажатий
Компонент ниже отслеживает количество нажатий кнопки. В нём использование рефа (а не состояния) уместно, поскольку счётчик нажатий считывается и перезаписывается только внутри обработчиков событий.
import { useRef } from 'react'; export default function Counter() { let ref = useRef(0); function handleClick() { ref.current = ref.current + 1; alert('Вы нажали ' + ref.current + ' раз(а)!'); } return ( <button onClick={handleClick}> Нажми! </button> ); }
При этом если отобразить {ref.current}
в JSX, то счётчик не будет обновляться по нажатию, поскольку изменение ref.current
не вызывает ререндер. Информацию, которую необходимо отображать на экране, следует хранить в состоянии.
Управление DOM при помощи рефов
Особенно часто рефы используются для управления DOM-узлами.
Для этого нужно создать объект рефа с изначальным значением null
:
import { useRef } from 'react';
function MyComponent() {
const inputRef = useRef(null);
// ...
И затем передать его как атрибут ref
в тот JSX, чьим DOM-узлом вы хотите управлять:
// ...
return <input ref={inputRef} />;
После того как React создаст DOM-узел и отобразит его на экране, ссылка на него будет сохранена в свойство current
. Теперь при помощи рефа можно получать доступ к DOM-узлу <input>
и вызывать различные его методы. Например, focus()
:
function handleClick() {
inputRef.current.focus();
}
React установит свойство current
обратно в null
, если DOM-узел будет удалён.
Больше про управление DOM при помощи рефов.
Example 1 of 4: Фокусировка input
В этом примере нажатие кнопки сфокусирует input:
import { useRef } from 'react'; export default function Form() { const inputRef = useRef(null); function handleClick() { inputRef.current.focus(); } return ( <> <input ref={inputRef} /> <button onClick={handleClick}> Сфокусировать input </button> </> ); }
Избежание пересоздания содержимого рефов
React сохраняет изначальное значение рефа один раз и при последующих рендерах игнорирует его.
function Video() {
const playerRef = useRef(new VideoPlayer());
// ...
Хотя результат вызова new VideoPlayer()
используется только при первоначальном рендере, эта функция всё ещё вызывается при всех последующих рендерах. Такое поведение может быть ресурсозатратным, если речь идёт о создании дорогостоящих объектов.
Чтобы этого избежать, реф можно инициализировать вот так:
function Video() {
const playerRef = useRef(null);
if (playerRef.current === null) {
playerRef.current = new VideoPlayer();
}
// ...
Хотя перезаписывать или считывать ref.current
во время рендера не разрешено, в этом случае это нормально, поскольку результат всегда один и тот же, и условие выполняется только во время инициализации (что полностью предсказуемо).
Deep Dive
Если вы используете тайп-чекер и не хотите постоянно проверять значение на null
, можно воспользоваться следующим паттерном:
function Video() {
const playerRef = useRef(null);
function getPlayer() {
if (playerRef.current !== null) {
return playerRef.current;
}
const player = new VideoPlayer();
playerRef.current = player;
return player;
}
// ...
В примере выше playerRef
может принимать значение null
. При этом можно убедить тайп-чекер в том, что getPlayer()
никогда не возвратит null
, и использовать в обработчиках событий именно его.
Диагностика неполадок
Не могу передать реф в свой компонент
Если передавать ref
в свой компонент так:
const inputRef = useRef(null);
return <MyInput ref={inputRef} />;
То можно получить такую ошибку:
По умолчанию пользовательские компоненты не передают рефы ни на какие DOM-узлы внутри них.
Чтобы это исправить, сперва найдите компонент, которому хотите передать реф:
export default function MyInput({ value, onChange }) {
return (
<input
value={value}
onChange={onChange}
/>
);
}
И затем оберните его в forwardRef
:
import { forwardRef } from 'react';
const MyInput = forwardRef(({ value, onChange }, ref) => {
return (
<input
value={value}
onChange={onChange}
ref={ref}
/>
);
});
export default MyInput;
Теперь родительский компонент может передать реф в MyInput
и получить доступ к его DOM-узлу <input>
.