Перейти до основного вмісту

Хук useRef

Рефи дозволяють отримати прямий доступ до DOM-вузлів або React-елементів із шаблону компонента. Вони використовуються якщо необхідно звернутися до імперативних методів та властивостям елемента.

  • Фокус елемент під час події, виділення тексту
  • Контроль програвання медіаконтенту
  • Інтеграція з DOM-бібліотеками
  • Доступ до DOM-властивостей, значення яких неможливо отримати по-іншому - розміри елемента, значення скрола тощо.

Створення

Рефи створюються хуком useRef() та прив'язані до React-елементів за допомогою атрибуту ref (скорочення від reference), який зберігатиме посилання на DOM-елемент.

import { useRef } from "react";

const App = () => {
const btnRef = useRef();

return <button ref={btnRef}>Button with ref</button>;
};

Життєвий цикл рефа

React надає властивості current посилання на DOM-елемент коли компонент монтується та null при розмонтуванні, тому значення рефа доступне тільки після монтування.

import { useState, useRef } from "react";

const App = () => {
const [value, setValue] = useState(0);
const btnRef = useRef();

// Буде null на першому рендері
// і посиланням на DOM-елемент всі наступні
console.log(btnRef.current);

useEffect(() => {
// Ефект виконується після монтування,
// тому завжди буде посиланням на DOM-елемент
console.log(btnRef.current);
});

const handleClick = () => {
// Кліки будуть після монтування,
// тому завжди буде посиланням на DOM-елемент
console.log(btnRef.current);
};

return (
<>
<button onClick={() => setValue(value + 1)}>
Update value to trigger re-render
</button>
<button ref={btnRef} onClick={handleClick}>
Button with ref
</button>
</>
);
};

Відсутність реактивності

Рефи це не стан, тобто вони не реактивні, тому зміна значення рефа не впливає на оновлення компонента і не викликає ре-рендер.

import { useEffect, useRef } from "react";

const App = () => {
const valueRef = useRef(0);

useEffect(() => {
// Виконається лише один раз під час монтування.
// Наступні оновлення значення рефа не
// викличуть оновлення компонента
console.log(valueRef.current);
});

const handleClick = () => {
valueRef.current += 1;
};

return <button onClick={handleClick}>Click to update ref value</button>;
};
Початкове значення

Рефи також можна використовувати як сховище довільних значень, які не змінюються між рендерами компонента та на нього не впливають. Тому в прикладі хуку useRef передано початкове значення якості current - число 0. Ця можливість використовується для класу завдань при створенні складніших компонентів.

const valueRef = useRef(0);

Простий відеоплеєр

Створимо компонент Player для програвання відео, використовуючи нативний тег <video>. Щоб запустити та зупинити програвання необхідно викликати методи HTMLMediaElement.play() та HTMLMediaElement.pause(), де HTMLMediaElement це елемент <video>. Використовуємо реф для отримання доступу до DOM-елементу та його методам.

import { useRef } from "react";

const Player = ({ source }) => {
const playerRef = useRef();
const play = () => playerRef.current.play();
const pause = () => playerRef.current.pause();

return (
<div>
<video ref={playerRef} src={source}>
Sorry, your browser does not support embedded videos.
</video>
<div>
<button onClick={play}>Play</button>
<button onClick={pause}>Pause</button>
</div>
</div>
);
};

const App = () => {
return <Player source="http://media.w3.org/2010/05/sintel/trailer.mp4" />;
};

Перенаправлення рефів

При використанні рефів на компоненті, проп ref не передається автоматично. Це створює проблеми у випадку коли ми хочемо отримати ref на елемент усередині самого компонента, а не на сам компонент. Функція forwardRef автоматично передає пропси, отримані батьківським компонентом його дочірнім елементам.

import { forwardRef, useRef, useEffect } from "react";

const CustomButton = forwardRef((props, ref) => (
<button ref={ref}>{props.children}</button>
));

const App = () => {
const btnRef = useRef();

useEffect(() => btnRef.current.focus(), []);

return <CustomButton ref={btnRef}>Button with forwarded ref</CustomButton>;
};

Такий підхід дозволяє отримати посилання у батьківському компоненті на DOM-елемент усередині іншого компонента. Наприклад, ви створюєте галерею, так можна отримати посилання на DOM-елементи поза них і працювати з їх властивостями, наприклад використовувати метод Element.getBoundingClientRect() і тому подібне.