Хотите заказать веб-сайт? Связаться с нами

Refs — прямой доступ к DOM и хранение данных без рендера

Цель урока: научиться использовать Refs для прямого доступа к DOM-элементам, управления фокусом, анимациями, интеграции со сторонними библиотеками и хранения mutable-значений, которые не вызывают перерендер компонента.

Refs — прямой доступ к DOM и хранение данных без рендера

Введение: что такое Refs и зачем они нужны

Refs (ссылки) — это механизм React, который позволяет получить прямой доступ к DOM-элементам или React-компонентам.

Главная мысль:

Ref — это как «кабель», который соединяет React-компонент с реальным DOM-элементом. Через него можно делать то, что невозможно сделать через React-подход.

Когда нужны Refs:

  1. Управление фокусом, выделением текста, медиа-плеерами
  2. Запуск анимаций (не через React-состояние)
  3. Интеграция со сторонними DOM-библиотеками
  4. Хранение предыдущих значений
  5. Хранение значений, которые не должны вызывать перерендер

Когда НЕ нужны Refs:

  1. Для данных, которые должны обновлять интерфейс (используй state)
  2. Вместо управления состоянием формы (используй state или библиотеки)

Основы использования useRef

Синтаксис:

const ref = useRef(initialValue);
// ref.current — доступ к значению

Простейший пример: фокус на поле

import { useRef, useEffect } from 'react';

function AutoFocusInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    // После монтирования ставим фокус на поле
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} placeholder="Введите текст..." />;
}

Что здесь происходит:

  1. useRef(null) создаёт объект { current: null }
  2. ref={inputRef} связывает DOM-элемент с этим объектом
  3. После монтирования inputRef.current указывает на реальный DOM-элемент <input>
  4. Вызываем метод focus() напрямую

Управление DOM-элементами через Refs

Примеры с разными методами:

function InputControls() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus();
  };

  const blurInput = () => {
    inputRef.current.blur();
  };

  const selectText = () => {
    inputRef.current.select();
  };

  const getValue = () => {
    alert(inputRef.current.value);
  };

  return (
    <div>
      <input ref={inputRef} defaultValue="Пример текста" />
      
      <button onClick={focusInput}>Фокус</button>
      <button onClick={blurInput}>Убрать фокус</button>
      <button onClick={selectText}>Выделить текст</button>
      <button onClick={getValue}>Показать значение</button>
    </div>
  );
}

Refs для работы с медиа-элементами

function VideoPlayer({ src }) {
  const videoRef = useRef(null);
  const [isPlaying, setIsPlaying] = useState(false);

  const play = () => {
    videoRef.current.play();
    setIsPlaying(true);
  };

  const pause = () => {
    videoRef.current.pause();
    setIsPlaying(false);
  };

  const togglePlay = () => {
    if (isPlaying) {
      pause();
    } else {
      play();
    }
  };

  const restart = () => {
    videoRef.current.currentTime = 0;
    videoRef.current.play();
  };

  return (
    <div>
      <video 
        ref={videoRef} 
        src={src} 
        width="400" 
        controls={false}
      />
      <div>
        <button onClick={togglePlay}>
          {isPlaying ? '⏸️ Пауза' : '▶️ Воспроизвести'}
        </button>
        <button onClick={restart}>⏪ С начала</button>
      </div>
    </div>
  );
}
Из ноутбука появляются летающие светящиеся экраны

Оживи свой сайт. Освой JavaScript!

Статичная верстка — это только скелет. Наш онлайн-курс "JavaScript с нуля до профи" даст твоим страницам мышцы и нервы. Научись создавать слайдеры, формы, интерактивные карты и получать данные с сервера.

От теории — к реальным скриптам в твоём портфолио.

Подробнее о курсе

Refs как mutable-контейнер (без перерендера)

Основное свойство: изменение ref.current НЕ вызывает перерендер компонента.

function Timer() {
  const [count, setCount] = useState(0);
  const intervalRef = useRef(null);
  const [isRunning, setIsRunning] = useState(false);

  const startTimer = () => {
    if (intervalRef.current) return; // Уже запущен
    
    intervalRef.current = setInterval(() => {
      setCount(prev => prev + 1);
    }, 1000);
    
    setIsRunning(true);
  };

  const stopTimer = () => {
    clearInterval(intervalRef.current);
    intervalRef.current = null;
    setIsRunning(false);
  };

  const resetTimer = () => {
    stopTimer();
    setCount(0);
  };

  return (
    <div>
      <p>Секунд: {count}</p>
      <button onClick={startTimer} disabled={isRunning}>Старт</button>
      <button onClick={stopTimer} disabled={!isRunning}>Стоп</button>
      <button onClick={resetTimer}>Сброс</button>
    </div>
  );
}

Почему это работает:

  1. intervalRef хранит ID таймера
  2. Изменение intervalRef.current не вызывает перерендер
  3. Таймер продолжает работать, даже если компонент перерендерится

Хранение предыдущих значений

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)}>+1</button>
    </div>
  );
}

Refs для callback-функций (Callback Refs)

Кроме useRef, можно использовать callback-функцию:

function MeasuredComponent() {
  const [height, setHeight] = useState(0);

  const measuredRef = useCallback((node) => {
    if (node !== null) {
      setHeight(node.getBoundingClientRect().height);
    }
  }, []);

  return (
    <div>
      <p ref={measuredRef}>Измеряемый текст</p>
      <p>Высота элемента: {height}px</p>
    </div>
  );
}

Когда использовать:

  1. Нужно измерить DOM-элемент
  2. Нужно выполнить действие при монтировании/размонтировании элемента
  3. Работа с динамическими списками элементов

Refs в классовых компонентах (для понимания)

class InputFocus extends React.Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }

  componentDidMount() {
    this.inputRef.current.focus();
  }

  render() {
    return <input ref={this.inputRef} />;
  }
}

Forwarding Refs (передача Ref в дочерний компонент)

Иногда нужно передать ref в дочерний компонент:

import { forwardRef, useRef } from 'react';

// Дочерний компонент, который принимает ref
const FancyInput = forwardRef((props, ref) => {
  return (
    <input 
      ref={ref}
      style={{ border: '2px solid blue', padding: '8px' }}
      {...props}
    />
  );
});

// Родительский компонент
function Parent() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <FancyInput ref={inputRef} placeholder="Напиши что-нибудь" />
      <button onClick={focusInput}>Фокус</button>
    </div>
  );
}

Когда нужно:

  1. Библиотеки компонентов (Material-UI, Ant Design)
  2. Сложные компоненты, скрывающие DOM-структуру
  3. Интеграция с сторонними библиотеками

useImperativeHandle — кастомные методы через Refs

Позволяет создать свои методы, доступные через ref:

import { forwardRef, useImperativeHandle, useRef } from 'react';

const CustomInput = forwardRef((props, ref) => {
  const inputRef = useRef(null);

  // Определяем, какие методы будут доступны через ref
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    shake: () => {
      const input = inputRef.current;
      input.style.transform = 'translateX(-10px)';
      setTimeout(() => {
        input.style.transform = 'translateX(10px)';
        setTimeout(() => {
          input.style.transform = 'translateX(0)';
        }, 100);
      }, 100);
    },
    getValue: () => {
      return inputRef.current.value;
    }
  }));

  return <input ref={inputRef} {...props} />;
});

function Parent() {
  const inputRef = useRef(null);

  const handleShake = () => {
    inputRef.current.shake();
  };

  const handleGetValue = () => {
    alert(inputRef.current.getValue());
  };

  return (
    <div>
      <CustomInput ref={inputRef} placeholder="Введите текст" />
      <button onClick={handleShake}>Тряхнуть</button>
      <button onClick={handleGetValue}>Показать значение</button>
    </div>
  );
}

Когда использовать:

  1. Создание собственных библиотек компонентов
  2. Сокрытие сложной логики внутри компонента
  3. Предоставление удобного API для разработчиков

Refs и анимации

function AnimatedBox() {
  const boxRef = useRef(null);
  const [isAnimating, setIsAnimating] = useState(false);

  const animateBox = () => {
    const box = boxRef.current;
    if (!box) return;

    setIsAnimating(true);
    
    // Анимация через CSS-трансформации
    box.style.transition = 'transform 0.5s ease';
    box.style.transform = 'translateX(100px) rotate(45deg)';

    setTimeout(() => {
      box.style.transform = 'translateX(0) rotate(0deg)';
      setTimeout(() => {
        setIsAnimating(false);
      }, 500);
    }, 500);
  };

  return (
    <div>
      <div 
        ref={boxRef}
        style={{
          width: '100px',
          height: '100px',
          background: 'blue',
          margin: '20px 0'
        }}
      />
      <button onClick={animateBox} disabled={isAnimating}>
        {isAnimating ? 'Анимация...' : 'Анимировать'}
      </button>
    </div>
  );
}

Refs vs State (сравнение)

Характеристика Refs (useRef) State (useState)
Изменение вызывает перерендер? ❌ Нет ✅ Да
Хранит данные между рендерами? ✅ Да ✅ Да
Доступ к DOM-элементам? ✅ Да ❌ Нет
Может управлять интерфейсом? ❌ Нет ✅ Да
Подходит для таймеров? ✅ Да ⚠️ Только с осторожностью
Где хранить предыдущее значение? ✅ Да ❌ Нет

Частые ошибки новичков

Ошибка Почему Исправление
ref.current равен null Элемент ещё не смонтировался Используй useEffect или проверку
Меняют ref в useEffect без проверки Может вызвать ошибку Проверяй if (ref.current)
Используют ref для состояния интерфейса Не вызывает перерендер Используй useState
Не используют forwardRef ref не доходит до DOM-элемента Оберни компонент в forwardRef
Изменяют ref напрямую в JSX Неправильный синтаксис Правильно: ref={myRef}

Итог (что мы узнали)

Что узнали Как это работает
useRef Создание ссылки на DOM-элемент или хранилище
DOM-доступ Прямые методы: focus, blur, play, pause
mutable-контейнер Хранение данных без перерендера
usePrevious Хранение предыдущего значения
forwardRef Передача ref в дочерние компоненты
useImperativeHandle Создание кастомных методов через ref
Refs vs State Когда что использовать

Задание для закрепления (на 1–2 часа)

Создай компонент MusicPlayer, который:

  1. Использует useRef для доступа к <audio> элементу
  2. Имеет кнопки: ▶️ Воспроизвести, ⏸️ Пауза, ⏹️ Остановить, ⏪ Перемотать на 10 секунд назад
  3. Показывает текущее время трека через state (обновляется каждую секунду через setInterval)
  4. Использует useRef для хранения intervalId (чтобы очищать при размонтировании)
  5. В useEffect добавляет обработчик события ended (когда трек закончился — автоматически перематывает на начало)

Дополнительно: Создай второй компонент AutoFocusForm:

  1. При монтировании устанавливает фокус на первое поле
  2. При сабмите формы переключает фокус на следующее поле (через refs)

Проверка: убедись, что плеер работает, а форма переключает фокус между полями.

Что дальше?

В следующем уроке разберём Render Props — паттерн, который позволяет делиться кодом между компонентами через функцию-пропс. Ты узнаешь, как создавать гибкие компоненты, которые могут рендерить разный контент в зависимости от переданной функции.

Теги: