Вызывает ли изменение useRef перерисовку компонента
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Вызывает ли изменение useRef перерисовку компонента
НЕТ. Изменение useRef НЕ вызывает перерисовку компонента. Это одно из ключевых отличий useRef от useState. Расскажу подробно.
1. useRef — хранилище без перерисовки
import { useRef, useState } from 'react';
function Counter() {
const countRef = useRef(0);
const [count, setCount] = useState(0);
const handleRef = () => {
countRef.current++; // Изменяем, но перерисовки НЕ будет
console.log('countRef:', countRef.current); // 1, 2, 3...
};
const handleState = () => {
setCount(count + 1); // Изменяем и вызываем перерисовку
};
return (
<div>
<p>Ref: {countRef.current}</p> <!-- Всегда 0 на экране -->
<p>State: {count}</p> <!-- Обновляется на экране -->
<button onClick={handleRef}>Increment Ref</button>
<button onClick={handleState}>Increment State</button>
</div>
);
}
Результат:
- Кнопка 1 нажата 5 раз: countRef.current = 5, но на экране всё ещё показывает 0
- Кнопка 2 нажата 1 раз: count = 1, на экране обновляется
2. Откуда это видно
useRef не вызывает re-render:
function Example() {
const renderCount = useRef(0);
// Эта функция вызывается при КАЖДОМ render
renderCount.current++;
console.log('Component rendered, times:', renderCount.current);
return (
<div>
<p>This component rendered {renderCount.current} times</p>
<button onClick={() => {
// Это НЕ вызовет re-render
renderCount.current = 100;
}}>
Change Ref (no re-render)
</button>
</div>
);
}
Вывод в консоль:
Component rendered, times: 1
Component rendered, times: 2 (после нажатия кнопки)
Component rendered, times: 3
На экране:
- После нажатия кнопки: "This component rendered 2 times" (потому что перерисовки не было, значение 100 не отобразилось)
3. Сравнение useRef vs useState
function Comparison() {
const ref = useRef(0);
const [state, setState] = useState(0);
const renderCount = useRef(0);
renderCount.current++;
return (
<div>
<p>Renders: {renderCount.current}</p>
<p>Ref value: {ref.current}</p>
<p>State value: {state}</p>
<button onClick={() => {
ref.current++; // Не вызывает re-render
}}>Change Ref</button>
<button onClick={() => {
setState(s => s + 1); // Вызывает re-render
}}>Change State</button>
</div>
);
}
Нажимаем кнопку "Change Ref" 5 раз:
- Renders: 1 (не увеличивается)
- Ref value: 5 (увеличивается в консоли, но не видно на экране)
Нажимаем кнопку "Change State" 1 раз:
- Renders: 2 (увеличивается, произошла перерисовка)
- State value: 1 (видно на экране)
4. Практическое применение: фокус на input
function TextInput() {
const inputRef = useRef(null);
const handleFocus = () => {
// Получаем доступ к DOM элементу
inputRef.current.focus();
// Это НЕ вызывает перерисовку компонента
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleFocus}>Focus Input</button>
</div>
);
}
Почему это важно: Нам нужно управлять фокусом, но это не требует перерисовки. useState был бы излишним.
5. Практическое применение: таймеры
function Timer() {
const intervalRef = useRef(null);
const timeRef = useRef(0);
const [display, setDisplay] = useState(0);
const handleStart = () => {
// Сохраняем ID интервала в ref
intervalRef.current = setInterval(() => {
timeRef.current++; // Это НЕ вызывает перерисовку
setDisplay(timeRef.current); // Это ВЫЗЫВАЕТ перерисовку
}, 1000);
};
const handleStop = () => {
clearInterval(intervalRef.current);
};
return (
<div>
<p>Time: {display}</p>
<button onClick={handleStart}>Start</button>
<button onClick={handleStop}>Stop</button>
</div>
);
}
6. useRef сохраняет значение между renders
function Example() {
const ref = useRef(0);
const [, setTrigger] = useState(0);
// Эта функция вызывается при КАЖДОМ render
ref.current++;
return (
<div>
<p>Ref current: {ref.current}</p>
<button onClick={() => setTrigger(t => t + 1)}>
Trigger Re-render
</button>
</div>
);
}
Первый render:
- ref.current = 1
- На экране: "Ref current: 1"
После нажатия кнопки (re-render):
- ref.current = 2 (значение сохранилось и увеличилось!)
- На экране: "Ref current: 2"
7. useRef для хранения предыдущего значения
function Component({ value }) {
const prevValueRef = useRef();
useEffect(() => {
prevValueRef.current = value; // Сохраняем текущее значение
}, [value]);
return (
<div>
<p>Current: {value}</p>
<p>Previous: {prevValueRef.current}</p>
</div>
);
}
Когда value изменяется:
- Компонент перерисовывается (потому что value в зависимостях)
- На экране показываем текущее значение
- В useEffect сохраняем это значение в ref
- При следующем изменении value, в prevValueRef.current будет старое значение
8. Когда useRef вызывает обновление экрана
Некоторые операции вызывают КОСВЕННЫЙ re-render:
function Example() {
const ref = useRef({ count: 0 });
const handleClick = () => {
// Это изменяет объект в памяти
ref.current.count++;
// НО это НЕ вызовет re-render
// ref.current всё ещё указывает на тот же объект
};
return (
<div>
<p>Count: {ref.current.count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}
На экране всегда будет 0, даже если count = 100 в памяти.
9. useCallback и useRef
function Parent() {
const callCountRef = useRef(0);
const [, setTrigger] = useState(0);
const memoizedCallback = useCallback(() => {
callCountRef.current++; // Не вызывает re-render
console.log('Called', callCountRef.current);
}, []);
return (
<div>
<p>Call count: {callCountRef.current}</p>
<button onClick={memoizedCallback}>Call Callback</button>
<button onClick={() => setTrigger(t => t + 1)}>Re-render</button>
</div>
);
}
Нажимаем "Call Callback" 5 раз:
- Консоль: Called 1, Called 2, Called 3...
- Экран: "Call count: 0" (не обновляется)
Нажимаем "Re-render":
- Экран обновляется и показывает "Call count: 5"
10. Чтобы useRef вызвал re-render
Нужно явно вызвать setState:
function Example() {
const ref = useRef(0);
const [, setRender] = useState(0);
const handleClick = () => {
ref.current++;
// Явно вызываем re-render
setRender(prev => prev + 1);
};
return (
<div>
<p>Ref: {ref.current}</p>
<button onClick={handleClick}>Increment and Re-render</button>
</div>
);
}
11. useLayoutEffect и useRef
function Example() {
const ref = useRef(null);
useLayoutEffect(() => {
// useLayoutEffect вызывается ПОСЛЕ render, но ДО paint
if (ref.current) {
ref.current.focus();
// Это НЕ вызывает НОВЫЙ re-render
}
}, []);
return <input ref={ref} />;
}
Таблица сравнения
| Свойство | useRef | useState |
|---|---|---|
| Вызывает re-render | НЕТ | ДА |
| Сохраняет между renders | ДА | ДА |
| Возвращает новый объект | НЕТ (тот же ref) | ДА (новое значение) |
| Синхронный доступ | ДА | НЕТ (асинхронный) |
| Использование для | DOM, таймеры, переменные | Видимые данные |
Ключевой вывод
НЕТ, изменение useRef НЕ вызывает перерисовку компонента.
УseRef используется для:
- Доступа к DOM элементам (фокус, значение input)
- Хранения значений, которые не влияют на UI (таймеры, счётчики)
- Сохранения значений между renders (предыдущее значение, ID интервала)
Если нужна перерисовка при изменении значения — используй useState.
Если нужно только хранить значение без перерисовки — используй useRef.