Я читаю о React useState() и useRef() в "Часто задаваемые вопросы по хукам", и я запутался в некоторых случаях использования, которые, кажется, имеют решение с помощью useRef и useState одновременно, и я не уверен, как правильно.
Из "Часто задаваемых вопросов по хукам" об использованииRef():
"The useRef() Hook isn’t just for DOM refs. The “ref” object is a generic container whose current property is mutable and can hold any value, similar to an instance property on a class."
С использоватьСсылка():
function Timer() {
const intervalRef = useRef();
useEffect(() => {
const id = setInterval(() => {
// ...
});
intervalRef.current = id;
return () => {
clearInterval(intervalRef.current);
};
});
// ...
}
С использовать состояние():
function Timer() {
const [intervalId, setIntervalId] = useState(null);
useEffect(() => {
const id = setInterval(() => {
// ...
});
setIntervalId(id);
return () => {
clearInterval(intervalId);
};
});
// ...
}
Оба примера будут иметь одинаковый результат, но какой из них лучше - и почему?





Основное различие между ними заключается в следующем:
useState вызывает повторный рендеринг, useRef нет.
Общим между ними является то, что и useState, и useRef могут запоминать свои данные после повторного рендеринга. Поэтому, если ваша переменная определяет рендеринг слоя представления, используйте useState. В противном случае используйте useRef
Я бы посоветовал прочитать этот статья.
Действительно ли имеет такое значение на практике введение отдельного (сбивающего с толку) хука? React делает много вещей против оптимальной производительности (регистрируя и удаляя вещи), но мы должны позаботиться о том, чтобы избежать повторного рендеринга? Почему?
Когда вы используете useRef с собственным элементом ввода HTML, ваш компонент является неконтролируемым вводом, а при использовании useState его контролируемым
В основном, мы используем UseState в тех случаях, когда значение состояния должно обновляться при повторном рендеринге.
когда вы хотите, чтобы ваша информация сохранялась на протяжении всего срока службы компонента, вы будете использовать ИспользованиеСсылка, потому что это просто не для работы с повторным рендерингом.
Если вы сохраните идентификатор интервала, единственное, что вы можете сделать, это завершить интервал. Что лучше, так это сохранить состояние timerActive, чтобы вы могли останавливать/запускать таймер, когда это необходимо.
function Timer() {
const [timerActive, setTimerActive] = useState(true);
useEffect(() => {
if (!timerActive) return;
const id = setInterval(() => {
// ...
});
return () => {
clearInterval(intervalId);
};
}, [timerActive]);
// ...
}
Если вы хотите, чтобы обратный вызов менялся при каждом рендеринге, вы можете использовать ссылку для обновления внутреннего обратного вызова при каждом рендеринге.
function Timer() {
const [timerActive, setTimerActive] = useState(true);
const callbackRef = useRef();
useEffect(() => {
callbackRef.current = () => {
// Will always be up to date
};
});
useEffect(() => {
if (!timerActive) return;
const id = setInterval(() => {
callbackRef.current()
});
return () => {
clearInterval(intervalId);
};
}, [timerActive]);
// ...
}
useRef полезен, когда вы хотите отслеживать изменение значения, но не хотите запускать повторную визуализацию или useEffect этим.
Наиболее часто используется случай, когда у вас есть функция, которая зависит от значения, но значение должно быть обновлено самим результатом функции.
Например, предположим, что вы хотите разбить на страницы некоторый результат API:
const [filter, setFilter] = useState({});
const [rows, setRows] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const fetchData = useCallback(async () => {
const nextPage = currentPage + 1;
const response = await fetchApi({...filter, page: nextPage});
setRows(response.data);
if (response.data.length) {
setCurrentPage(nextPage);
}
}, [filter, currentPage]);
fetchData использует состояние currentPage, но его необходимо обновить currentPage после успешного ответа. Это неизбежный процесс, но он может вызвать бесконечный цикл, также известный как Maximum update depth exceeded error в React. Например, если вы хотите получать строки при загрузке компонента, вы хотите сделать что-то вроде этого:
useEffect(() => {
fetchData();
}, [fetchData]);
Это глючит, потому что мы используем состояние и обновляем его в той же функции.
Мы хотим отслеживать currentPage, но не хотим запускать useCallback или useEffect его изменением.
Мы можем легко решить эту проблему с помощью useRef:
const currentPageRef = useRef(0);
const fetchData = useCallback(async () => {
const nextPage = currentPageRef.current + 1;
const response = await fetchApi({...filter, page: nextPage});
setRows(response.data);
if (response.data.length) {
currentPageRef.current = nextPage;
}
}, [filter]);
Мы можем удалить зависимость currentPage из массива useCallback deps с помощью useRef, таким образом, наш компонент будет сохранен от бесконечного цикла.
Вы также можете использовать useRef для ссылки на элемент dom (атрибут HTML по умолчанию)
например: назначение кнопки для фокусировки на поле ввода.
тогда как useState только обновляет значение и повторно отображает компонент.
Это действительно зависит в основном от того, для чего вы используете таймер, что неясно, поскольку вы не показали, что отображает компонент.
Если вы хотите использовать показать значение вашего таймера при рендеринге вашего компонента, вам нужно использовать useState. В противном случае изменение значения вашего ref не приведет к повторному рендерингу и таймер не будет обновляться на экране.
Если должно произойти что-то еще, что визуально должно быть изменить интерфейс в каждый тик таймера, вы используете useState и либо помещаете переменную timer в массив зависимостей хука useEffect (где вы делаете все, что необходимо для обновлений пользовательского интерфейса), либо выполняете свою логику в методе рендеринга. (возвращаемое значение компонента) на основе значения таймера. Вызовы SetState вызовут повторный рендеринг, а затем вызовут хуки useEffect (в зависимости от массива зависимостей). С ref никаких обновлений не произойдет, и не будет вызываться useEffect.
Если вы хотите использовать только использовать таймер внутри, вместо этого вы можете использовать useRef. Всякий раз, когда должно произойти что-то, что должно вызвать повторную визуализацию (т.е. по прошествии определенного времени), вы можете вызвать другую переменную состояния с помощью setState из вашего обратного вызова setInterval. Это приведет к повторному рендерингу компонента.
Использовать refs для локального состояния следует только тогда, когда это действительно необходимо (например, в случае проблем с потоком или производительностью), так как это не соответствует «способ реагирования».
useRef не перерисовываетсяЕсли вы создаете простое приложение-счетчик, используя useRef для хранения состояния:
import { useRef } from "react";
const App = () => {
const count = useRef(0);
return (
<div>
<h2>count: {count.current}</h2>
<button
onClick = {() => {
count.current = count.current + 1;
console.info(count.current);
}}
>
increase count
</button>
</div>
);
};
Если вы нажмете на кнопку, <h2>count: {count.current}</h2> это значение не изменится, потому что компонент НЕ ПОВТОРЯЕТСЯ. Если вы посмотрите на консоль console.info(count.current), вы увидите, что значение на самом деле увеличивается, но, поскольку компонент не перерисовывается, пользовательский интерфейс не обновляется.
Если вы установите состояние с помощью useState, нажатие на кнопку приведет к повторному отображению компонента, поэтому пользовательский интерфейс будет обновлен.
input.Рендеринг — дорогостоящая операция. В некоторых случаях вы не хотите продолжать перерисовывать приложение. Например, когда вы сохраняете входное значение в состоянии для создания управляемого компонента. В этом случае для каждого нажатия клавиши вы будете перерисовывать приложение. Если вы используете ref для получения ссылки на элемент DOM, то с useState вы перерисовываете компонент только один раз:
import { useState, useRef } from "react";
const App = () => {
const [value, setValue] = useState("");
const valueRef = useRef();
const handleClick = () => {
console.info(valueRef);
setValue(valueRef.current.value);
};
return (
<div>
<h4>Input Value: {value}</h4>
<input ref = {valueRef} />
<button onClick = {handleClick}>click</button>
</div>
);
};
useEffectчтобы создать простую анимацию перелистывания, нам нужно 2 значения состояния. одно — это логическое значение, чтобы переключаться или нет в интервале, другое — очищать подписку, когда мы покидаем компонент:
const [isFlipping, setIsFlipping] = useState(false);
let flipInterval = useRef<ReturnType<typeof setInterval>>();
useEffect(() => {
startAnimation();
return () => flipInterval.current && clearInterval(flipInterval.current);
}, []);
const startAnimation = () => {
flipInterval.current = setInterval(() => {
setIsFlipping((prevFlipping) => !prevFlipping);
}, 10000);
};
setInterval возвращает идентификатор, и мы передаем его clearInterval, чтобы завершить подписку, когда мы покидаем компонент. flipInterval.current либо нуль, либо этот идентификатор. Если бы мы не использовали здесь ref, каждый раз, когда мы переключались бы с null на id или с id на null, этот компонент перерисовывался бы, и это создавало бы бесконечный цикл.
useRef для хранения переменных состояния.Скажем, в приложении React Native мы устанавливаем звук для определенных действий, которые не влияют на пользовательский интерфейс. Для одной переменной состояния может быть не так много экономии производительности, но если вы играете в игру, и вам нужно установить другой звук в зависимости от статуса игры.
const popSoundRef = useRef<Audio.Sound | null>(null);
const pop2SoundRef = useRef<Audio.Sound | null>(null);
const winSoundRef = useRef<Audio.Sound | null>(null);
const lossSoundRef = useRef<Audio.Sound | null>(null);
const drawSoundRef = useRef<Audio.Sound | null>(null);
Если бы я использовал useState, я бы продолжал перерисовывать каждый раз, когда меняю значение состояния.
useRef() только обновляет значение, а не повторно отображает ваш пользовательский интерфейс, если вы хотите повторно отображать пользовательский интерфейс, тогда вам нужно использовать useState() вместо useRe. дайте мне знать, если потребуется какая-либо коррекция.
Еще одно большое отличие состоит в том, что установка состояния является асинхронной, а установка ссылки — синхронной.