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.

Примеры использования useRef

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 не вызывает ререндер. Информацию, которую необходимо отображать на экране, следует хранить в состоянии.

Pitfall

Не перезаписывайте и не считывайте ref.current во время рендера.

React ожидает, что компоненты будут вести себя как чистые функции:

  • Для одинакового набора входных данных (пропсов, состояния и контекста), компонент всегда должен возвращать одинаковый JSX.
  • Вызов компонента в другом порядке или с другими аргументами не должен повлиять на результаты других вызовов.

Перезаписывание или считывание рефа во время рендера не оправдывает эти ожидания.

function MyComponent() {
// ...
// 🚩 Не перезаписывайте рефы во время рендера
myRef.current = 123;
// ...
// 🚩 Не считывайте рефы во время рендера
return <h1>{myOtherRef.current}</h1>;
}

Рефы можно считывать или перезаписывать в обработчиках событий или эффектах.

function MyComponent() {
// ...
useEffect(() => {
// ✅ Можно считывать и перезаписывать рефы в эффектах
myRef.current = 123;
});
// ...
function handleClick() {
// ✅ Можно считывать и перезаписывать рефы в обработчиках событий
doSomething(myOtherRef.current);
}
// ...
}

Если вам необходимо что-то считать или перезаписать во время рендера, то вместо рефа стоит использовать состояние.

Хотя при нарушении этих правил компонент всё ещё может работать, большинство нововведений React будут полагаться именно на них. Больше о сохранении компонентов чистыми.


Управление 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 при помощи рефов.

Примеры управления DOM при помощи useRef

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

Если в вашем проекте есть проверка типов и вы не хотите постоянно проверять значение на 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} />;

То можно получить такую ошибку:

Console
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

По умолчанию пользовательские компоненты не передают рефы ни на какие 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>.

Больше о получении доступа к DOM-узлам других компонентов.