Skip to main content
Практика завдань

Refs у React (useRef, createref, forwardref)

Що таке Refs?

Refs (посилання) — це спосіб отримати прямий доступ до DOM-елементів або React-компонентів з коду.

Refs використовуються, коли вам потрібно:

  • Керувати фокусом, вибором тексту
  • Запускати анімації
  • Інтегруватися з бібліотеками сторонніх розробників
  • Вимірювати розміри елементів

useRef

useRef — це хук для створення refs у функціональних компонентх.

Доступ до DOM-елементів

javascript
import { useRef } from 'react'; function TextInput() { const inputRef = useRef(null); function handleClick() { // Отримати прямий доступ до input inputRef.current.focus(); } return ( <div> <input ref={inputRef} type="text" /> <button onClick={handleClick}>Фокус на Input</button> </div> ); }

Зберігання змінного значення

useRef можна використовувати для зберігання будь-якого значення, яке не викликає повторний рендер:

javascript
function Timer() { const [count, setCount] = useState(0); const intervalRef = useRef(null); function start() { if (intervalRef.current) return; intervalRef.current = setInterval(() => { setCount(c => c + 1); }, 1000); } function stop() { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } } useEffect(() => { return () => stop(); // Очистка }, []); return ( <div> <p>Кількість: {count}</p> <button onClick={start}>Почати</button> <button onClick={stop}>Зупинити</button> </div> ); }

useRef vs useState

javascript
// useState - викликає повторний рендер const [value, setValue] = useState(0); // useRef - НЕ викликає повторний рендер const valueRef = useRef(0); valueRef.current = 1; // Не викличе рендер

createRef

createRef використовується в класових компонентх:

javascript
class TextInput extends React.Component { constructor(props) { super(props); this.inputRef = React.createRef(); } handleClick = () => { this.inputRef.current.focus(); }; render() { return ( <div> <input ref={this.inputRef} type="text" /> <button onClick={this.handleClick}>Фокус</button> </div> ); } }

Важливо:

Не використовуйте createRef у функціональних компонентх! Нове посилання буде створено при кожному рендері. Використовуйте useRef.


forwardRef

forwardRef дозволяє передавати посилання через компонент до його дитини.

Проблема

javascript
function CustomInput(props) { return <input {...props} />; } // Не працює! ref не передається function Parent() { const inputRef = useRef(null); return <CustomInput ref={inputRef} />; // Помилка! }

Рішення

javascript
const CustomInput = forwardRef((props, ref) => { return <input ref={ref} {...props} />; }); function Parent() { const inputRef = useRef(null); function handleClick() { inputRef.current.focus(); } return ( <div> <CustomInput ref={inputRef} /> <button onClick={handleClick}>Фокус</button> </div> ); }

useImperativeHandle

useImperativeHandle дозволяє налаштувати, що доступно батьківському компоненту через ref.

Без useImperativeHandle

javascript
const CustomInput = forwardRef((props, ref) => { return <input ref={ref} />; }); // Батьківський компонент отримує доступ до всього DOM input

З useImperativeHandle

javascript
import { forwardRef, useImperativeHandle, useRef } from 'react'; const CustomInput = forwardRef((props, ref) => { const inputRef = useRef(null); useImperativeHandle(ref, () => ({ // Відкриваємо лише ці методи focus: () => { inputRef.current.focus(); }, scrollIntoView: () => { inputRef.current.scrollIntoView(); } // value, blur та інші методи недоступні })); return <input ref={inputRef} {...props} />; }); function Parent() { const inputRef = useRef(null); function handleClick() { inputRef.current.focus(); // Працює // inputRef.current.value; // undefined! } return ( <div> <CustomInput ref={inputRef} /> <button onClick={handleClick}>Фокус</button> </div> ); }

Практичні приклади

Автофокус при монтуванні

javascript
function AutoFocusInput() { const inputRef = useRef(null); useEffect(() => { inputRef.current.focus(); }, []); return <input ref={inputRef} />; }

Вимірювання розмірів елемента

javascript
function MeasureComponent() { const divRef = useRef(null); const [dimensions, setDimensions] = useState({}); useEffect(() => { if (divRef.current) { const { width, height } = divRef.current.getBoundingClientRect(); setDimensions({ width, height }); } }, []); return ( <div> <div ref={divRef} style={{ padding: 20, backgroundColor: 'lightblue' }}> Виміряй мене </div> <p>Ширина: {dimensions.width}px</p> <p>Висота: {dimensions.height}px</p> </div> ); }

Інтеграція з бібліотекою сторонніх розробників

javascript
function VideoPlayer({ src }) { const videoRef = useRef(null); useEffect(() => { // Ініціалізація плеєра з бібліотекою сторонніх розробників const player = new ThirdPartyPlayer(videoRef.current); player.load(src); return () => { player.destroy(); }; }, [src]); return <video ref={videoRef} />; }

Попереднє значення

javascript
function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }, [value]); return ref.current; } function Counter() { const [count, setCount] = useState(0); const prevCount = usePrevious(count); return ( <div> <p>Поточне: {count}</p> <p>Попереднє: {prevCount}</p> <button onClick={() => setCount(count + 1)}>+</button> </div> ); }

Callback Refs

Альтернативний спосіб роботи з refs за допомогою колбек-функції:

javascript
function Component() { const [height, setHeight] = useState(0); const measuredRef = useCallback(node => { if (node !== null) { setHeight(node.getBoundingClientRect().height); } }, []); return ( <div> <div ref={measuredRef}> <p>Висота цього div'а {height}px</p> </div> </div> ); }

Коли НЕ використовувати Refs

Не використовуйте для того, що можна зробити декларативно

javascript
// Погано function Dialog() { const dialogRef = useRef(null); function open() { dialogRef.current.style.display = 'block'; } function close() { dialogRef.current.style.display = 'none'; } return <div ref={dialogRef}>Діалог</div>; } // Добре function Dialog() { const [isOpen, setIsOpen] = useState(false); return isOpen ? <div>Діалог</div> : null; }

Не зберігайте дані, які впливають на рендеринг

javascript
// Погано function Component() { const dataRef = useRef([]); function addItem(item) { dataRef.current.push(item); // Компонент не перерендериться! } return <div>{dataRef.current.length} елементів</div>; } // Добре function Component() { const [data, setData] = useState([]); function addItem(item) { setData(prev => [...prev, item]); // Відбудеться повторний рендер } return <div>{data.length} елементів</div>; }

Типізація TypeScript

typescript
import { useRef, forwardRef, useImperativeHandle } from 'react'; // useRef з DOM-елементом function Component() { const inputRef = useRef<HTMLInputElement>(null); function handleClick() { inputRef.current?.focus(); } return <input ref={inputRef} />; } // forwardRef interface Props { placeholder?: string; } const CustomInput = forwardRef<HTMLInputElement, Props>((props, ref) => { return <input ref={ref} {...props} />; }); // useImperativeHandle interface CustomInputHandle { focus: () => void; reset: () => void; } const CustomInput = forwardRef<CustomInputHandle, Props>((props, ref) => { const inputRef = useRef<HTMLInputElement>(null); useImperativeHandle(ref, () => ({ focus() { inputRef.current?.focus(); }, reset() { if (inputRef.current) { inputRef.current.value = ''; } } })); return <input ref={inputRef} {...props} />; });

Загальні помилки

Доступ до ref.current до монтування

javascript
// Неправильно function Component() { const ref = useRef(null); console.log(ref.current); // null! Елемент ще не створено return <div ref={ref}>Привіт</div>; } // Правильно function Component() { const ref = useRef(null); useEffect(() => { console.log(ref.current); // Елемент доступний }, []); return <div ref={ref}>Привіт</div>; }

Створення нового ref при кожному рендері

javascript
// Неправильно function Component() { const ref = createRef(); // Новий ref при кожному рендері! return <div ref={ref}>Привіт</div>; } // Правильно function Component() { const ref = useRef(null); // Той самий ref return <div ref={ref}>Привіт</div>; }

Висновок

Refs у React:

  • useRef — для функціональних компонентів
  • createRef — для класових компонентів
  • forwardRef — для передачі ref через компонент
  • useImperativeHandle — для контролю доступу до ref
  • Не викликають повторний рендер при зміні
  • Використовуйте, коли декларативний підхід неможливий
  • Уникайте для того, що можна зробити через state

На співбесідах:

Важливо вміти:

  • Пояснити, що таке refs і коли їх використовувати
  • Показати різницю між useRef та useState
  • Пояснити, для чого потрібні forwardRef та useImperativeHandle
  • Навести приклади правильного використання refs
  • Описати, коли НЕ використовувати refs

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?
Практика завдань