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
, який ви пройшли. Пізніше ви можете змінити його на щось інше. Якщо ви передасте React об'єкт реф як атрибутref
вузлу JSX, React встановить його властивістьcurrent
.
На наступних рендерингах useRef
повертатиме той самий об'єкт.
Застереження
- Властивість
ref.current
можна мутувати. На відміну від стану, її можна змінювати. Однак, якщо вона містить об'єкт, який використовується для рендерингу (наприклад, фрагмент вашого стану), то вам не слід мутувати цей об'єкт. - Коли ви змінюєте властивість
ref.current
, React не перерендерить ваш компонент. React не знає, коли ви його змінюєте, тому що посилання - це звичайний JavaScript-об'єкт. - Не записуйте та не зчитуйте
ref.current
під час рендерингу, окрім ініціалізації. Це робить поведінку вашого компонента непередбачуваною. - У суворому режимі React викличе вашу функцію компонента двічі для того, щоб допомогти вам знайти випадкові домішки. Це поведінка лише для розробки і не впливає на виробництво. Кожен об'єкт ref буде створено двічі, але одну з версій буде відкинуто. Якщо ваша функція компонента є чистою (як і має бути), це не повинно вплинути на поведінку.
Використання
Посилання на значення за допомогою рефів
.Викличте useRef
на верхньому рівні вашого компонента, щоб оголосити одне або декілька посилань.
import { useRef } from 'react';
function Stopwatch() {
const intervalRef = useRef(0);
// ...
useRef
повертає об'єкт current
, щоб зберегти інформацію і прочитати її пізніше. Це може нагадати вам стан, але є важлива відмінність.
Зміна рефа не призводить до перерендерингу. Це означає, що рефи ідеально підходять для зберігання інформації, яка не впливає на візуальне виведення вашого компонента. Наприклад, якщо вам потрібно зберегти ідентифікатор інтервалу і отримати його пізніше, ви можете помістити його у реф. Щоб оновити значення всередині рефа, потрібно вручну змінити його властивість
function handleStartClick() {
const intervalId = setInterval(() => {
// ...
}, 1000);
intervalRef.current = intervalId;
}
Пізніше ви зможете прочитати ідентифікатор інтервалу з посилання, щоб викликати очистити цей інтервал:
function handleStopClick() {
const intervalId = intervalRef.current;
clearInterval(intervalId);
}
Використовуючи посилання, ви гарантуєте, що:
- Ви можете зберігати інформацію між відображеннями (на відміну від звичайних змінних, які скидаються при кожному рендері).
- Зміна не спричиняє перевідображення (на відміну від змінних стану, які спричиняють перевідображення).
- Інформація є локальною для кожної копії вашого компонента (на відміну від зовнішніх змінних, які є спільними).
Зміна рефа не спричиняє перерендерингу, тому рефи не підходять для зберігання інформації, яку ви хочете вивести на екран. Замість цього використовуйте стан. Докладніше про вибір між useRef
та useState
.
Лічильник кліків
Цей компонент використовує реф для відстеження кількості натискань на кнопку. Зверніть увагу, що тут можна використовувати реф замість стану, оскільки кількість кліків читається і записується лише в обробнику подій.
import { useRef } from 'react';
export default function Counter() {
let ref = useRef(0);
function handleClick() {
ref.current = ref.current + 1;
alert('You clicked ' + ref.current + ' times!');
}
return (
<button onClick={handleClick}>
Click me!
</button>
);
}
Якщо ви покажете {ref.current}
в JSX, номер не буде оновлюватися при кліці. Це пов'язано з тим, що значення ref.current
не викликає повторного рендерингу. Інформація, яка використовується для рендерингу, повинна бути станом.
Секундомір
У цьому прикладі використовується комбінація стану та посилань. І startTime
, і now
є змінними стану, оскільки вони використовуються для рендерингу. Але нам також потрібно тримати ідентифікатор інтервалу, щоб ми могли зупинити інтервал при натисканні кнопки. Оскільки ідентифікатор інтервалу не використовується для рендерингу, доцільно зберігати його у рефі та оновлювати вручну.
import { useState, useRef } from 'react';
export default function Stopwatch() {
const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);
const intervalRef = useRef(null);
function handleStart() {
setStartTime(Date.now());
setNow(Date.now());
clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => {
setNow(Date.now());
}, 10);
}
function handleStop() {
clearInterval(intervalRef.current);
}
let secondsPassed = 0;
if (startTime != null && now != null) {
secondsPassed = (now - startTime) / 1000;
}
return (
<>
<h1>Time passed: {secondsPassed.toFixed(3)}</h1>
<button onClick={handleStart}>
Start
</button>
<button onClick={handleStop}>
Stop
</button>
</>
);
}
Не записуйте та не читайте ref.current
під час рендерингу.
React очікує, що тіло вашого компонента поводиться як чиста функція:
- Якщо вхідні дані (пропси, стан та контекст) є однаковими, вона має повернути точно такий самий JSX.
- Виклик його в іншому порядку або з іншими аргументами не повинен впливати на результати інших викликів.
Читання або запис рефа під час рендерингу порушує ці очікування.
function MyComponent() {
// ...
// 🚩 Don't write a ref during rendering
myRef.current = 123;
// ...
// 🚩 Don't read a ref during rendering
return <h1>{myOtherRef.current}</h1>;
}
Замість цього можна читати або записувати посилання з обробників подій або ефектів.
function MyComponent() {
// ...
useEffect(() => {
// ✅ You can read or write refs in effects
myRef.current = 123;
});
// ...
function handleClick() {
// ✅ You can read or write refs in event handlers
doSomething(myOtherRef.current);
}
// ...
}
Якщо вам необхідно прочитати або записати щось під час рендерингу, використовуйте стан замість цього.
Коли ви порушуєте ці правила, ваш компонент може продовжувати працювати, але більшість нових функцій, які ми додаємо до React, будуть покладатися на ці очікування. Дізнайтеся більше про дотримання чистоти ваших компонентів.
Маніпулювання DOM за допомогою посилання
Особливо часто використовують реф для маніпулювання DOM. React має вбудовану підтримку для цього.
Спочатку оголосіть об'єкт
import { useRef } from 'react';
function MyComponent() {
const inputRef = useRef(null);
// ...
Потім передайте ваш об'єкт рефа як атрибут ref
до JSX вузла DOM, яким ви хочете маніпулювати:
// ...
return <input ref={inputRef} />;
Після того, як React створить DOM-вузол і виведе його на екран, React встановить властивість <input>
і викликати такі методи, як focus()
:
function handleClick() {
inputRef.current.focus();
}
React поверне властивість current
як null
, коли вузол буде видалено з екрану.
Детальніше про маніпуляції з DOM за допомогою посилань.
Фокусування текстового введення
У цьому прикладі натискання кнопки сфокусує введення:
import { useRef } from 'react';
export default function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} />
<button onClick={handleClick}>
Focus the input
</button>
</>
);
}
Прокрутка зображення у вікні перегляду
У цьому прикладі натискання на кнопку прокручує зображення. Він використовує посилання на вузол списку DOM, а потім викликає API DOM querySelectorAll
, щоб знайти зображення, до якого ми хочемо прокрутити.
import { useRef } from 'react';
export default function CatFriends() {
const listRef = useRef(null);
function scrollToIndex(index) {
const listNode = listRef.current;
// This line assumes a particular DOM structure:
const imgNode = listNode.querySelectorAll('li > img')[index];
imgNode.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center'
});
}
return (
<>
<nav>
<button onClick={() => scrollToIndex(0)}>
Tom
</button>
<button onClick={() => scrollToIndex(1)}>
Maru
</button>
<button onClick={() => scrollToIndex(2)}>
Jellylorum
</button>
</nav>
<div>
<ul ref={listRef}>
<li>
<img
src="https://placekitten.com/g/200/200"
alt="Tom"
/>
</li>
<li>
<img
src="https://placekitten.com/g/300/200"
alt="Maru"
/>
</li>
<li>
<img
src="https://placekitten.com/g/250/200"
alt="Jellylorum"
/>
</li>
</ul>
</div>
</>
);
}
div {
width: 100%;
overflow: hidden;
}
nav {
text-align: center;
}
button {
margin: .25rem;
}
ul,
li {
list-style: none;
white-space: nowrap;
}
li {
display: inline;
padding: 0.5rem;
}
Відтворення та призупинення відео
Цей приклад використовує реф для виклику play()
та pause()
на DOM-вузлі <video>
.
import { useState, useRef } from 'react';
export default function VideoPlayer() {
const [isPlaying, setIsPlaying] = useState(false);
const ref = useRef(null);
function handleClick() {
const nextIsPlaying = !isPlaying;
setIsPlaying(nextIsPlaying);
if (nextIsPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
}
return (
<>
<button onClick={handleClick}>
{isPlaying ? 'Pause' : 'Play'}
</button>
<video
width="250"
ref={ref}
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
>
<source
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
type="video/mp4"
/>
</video>
</>
);
}
button { display: block; margin-bottom: 20px; }
Виявлення посилання на власний компонент
Іноді вам може знадобитися дозволити батьківському компоненту маніпулювати DOM всередині вашого компонента. Наприклад, ви пишете компонент MyInput
, але хочете, щоб батьківський компонент міг фокусувати введення (до якого батьківський компонент не має доступу). Ви можете використати комбінацію useRef
, щоб утримувати введення, і forwardRef
, щоб показати його батьківському компоненту. Прочитайте детальний опис тут.
import { forwardRef, useRef } from 'react';
const MyInput = forwardRef((props, ref) => {
return <input {...props} ref={ref} />;
});
export default function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<MyInput ref={inputRef} />
<button onClick={handleClick}>
Focus the input
</button>
</>
);
}
Уникнення відтворення вмісту посилання
React зберігає початкове значення ref один раз і ігнорує його при наступних рендерах.
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
під час рендеру заборонено. Однак у цьому випадку це нормально, оскільки результат завжди однаковий, а умова виконується лише під час ініціалізації, тому він повністю передбачуваний.
Як уникнути перевірок на нуль при ініціалізації useRef пізніше
Якщо ви використовуєте перевірку типу і не хочете завжди перевіряти на 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
. Тоді використовуйте getPlayer()
у своїх обробниках подій.
Налагодження
Я не можу отримати посилання на користувацький компонент
Якщо ви спробуєте передати посилання
у власний компонент ось так:
const inputRef = useRef(null);
return <MyInput ref={inputRef} />;
Ви можете отримати помилку у консолі:
Застереження: Функціональним компонентам не можна давати рефи. Спроби отримати доступ до цього рефу будуть невдалими. Ви хотіли використати 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;
Тоді батьківський компонент може отримати посилання на нього.
Детальніше про доступ до вузлів DOM іншого компонента.