← Назад к вопросам

Как обновить ref?

1.7 Middle🔥 141 комментариев
#Soft Skills и рабочие процессы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Работа с Ref в React - обновление и лучшие практики

Ref - это мощный инструмент в React для прямого доступа к элементам DOM. Понимание того, как правильно обновлять и использовать ref, критично для опытного разработчика.

1. useRef и изменение значения

import { useRef, useEffect } from 'react';

function TextInput() {
  const inputRef = useRef<HTMLInputElement>(null);

  const handleFocus = () => {
    // Обновляем ref через присвоение
    if (inputRef.current) {
      inputRef.current.focus();
    }
  };

  return (
    <>
      <input ref={inputRef} type="text" />
      <button onClick={handleFocus}>Сфокусировать</button>
    </>
  );
}

2. Обновление значения ref

function Counter() {
  const countRef = useRef<number>(0);

  const increment = () => {
    // Обновляем значение ref напрямую
    countRef.current += 1;
    console.log(`Count: ${countRef.current}`);
    // Важно: это НЕ вызывает re-render!
  };

  const handleClick = () => {
    increment();
    // Компонент не перерендерится
  };

  return (
    <>
      <p>Count in ref: {countRef.current}</p>
      <button onClick={handleClick}>Увеличить</button>
    </>
  );
}

3. Ref с useEffect

function SearchInput() {
  const inputRef = useRef<HTMLInputElement>(null);
  const [query, setQuery] = useState('');

  // Обновляем DOM элемент через ref
  useEffect(() => {
    if (inputRef.current) {
      // Очищаем input
      inputRef.current.value = '';
      // Устанавливаем фокус
      inputRef.current.focus();
      // Устанавливаем placeholder
      inputRef.current.placeholder = 'Новый placeholder';
    }
  }, []);

  return <input ref={inputRef} onChange={(e) => setQuery(e.target.value)} />;
}

4. useImperativeHandle - раскрытие методов компонента

// Child компонент
interface InputHandle {
  focus: () => void;
  clear: () => void;
  getValue: () => string;
}

const Input = forwardRef<InputHandle, { placeholder: string }>(
  ({ placeholder }, ref) => {
    const inputRef = useRef<HTMLInputElement>(null);

    // Определяем методы, которые родитель может вызвать
    useImperativeHandle(ref, () => ({
      focus: () => {
        inputRef.current?.focus();
      },
      clear: () => {
        if (inputRef.current) {
          inputRef.current.value = '';
        }
      },
      getValue: () => {
        return inputRef.current?.value || '';
      },
    }));

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

// Parent компонент
function Form() {
  const inputRef = useRef<InputHandle>(null);

  const handleSubmit = () => {
    const value = inputRef.current?.getValue();
    console.log('Form value:', value);
  };

  const handleClear = () => {
    inputRef.current?.clear();
  };

  return (
    <>
      <Input ref={inputRef} placeholder="Введите текст" />
      <button onClick={handleClear}>Очистить</button>
      <button onClick={handleSubmit}>Отправить</button>
    </>
  );
}

5. Обновление ref внутри callback'а

function VideoPlayer() {
  const videoRef = useRef<HTMLVideoElement>(null);
  const [isPlaying, setIsPlaying] = useState(false);

  // Обновляем состояние через ref в callback
  const handlePlayPause = useCallback(() => {
    if (videoRef.current) {
      if (isPlaying) {
        videoRef.current.pause();
      } else {
        videoRef.current.play();
      }
      setIsPlaying(!isPlaying);
    }
  }, [isPlaying]);

  const handleSetTime = useCallback((time: number) => {
    if (videoRef.current) {
      videoRef.current.currentTime = time;
    }
  }, []);

  return (
    <>
      <video ref={videoRef} src="video.mp4" />
      <button onClick={handlePlayPause}>
        {isPlaying ? 'Пауза' : 'Воспроизведение'}
      </button>
      <button onClick={() => handleSetTime(10)}>Перейти к 10s</button>
    </>
  );
}

6. Работа с несколькими ref

function FormWithMultipleInputs() {
  const firstNameRef = useRef<HTMLInputElement>(null);
  const lastNameRef = useRef<HTMLInputElement>(null);
  const emailRef = useRef<HTMLInputElement>(null);

  const handleFocusNext = (current: React.RefObject<HTMLInputElement>) => {
    // Идем к следующему input
    const allInputs = [
      { ref: firstNameRef, name: 'firstName' },
      { ref: lastNameRef, name: 'lastName' },
      { ref: emailRef, name: 'email' },
    ];

    const currentIndex = allInputs.findIndex((input) => input.ref === current);
    const nextInput = allInputs[currentIndex + 1];
    nextInput?.ref.current?.focus();
  };

  return (
    <>
      <input
        ref={firstNameRef}
        type="text"
        placeholder="Имя"
        onKeyPress={(e) => e.key === 'Enter' && handleFocusNext(firstNameRef)}
      />
      <input
        ref={lastNameRef}
        type="text"
        placeholder="Фамилия"
        onKeyPress={(e) => e.key === 'Enter' && handleFocusNext(lastNameRef)}
      />
      <input
        ref={emailRef}
        type="email"
        placeholder="Email"
        onKeyPress={(e) => e.key === 'Enter' && handleFocusNext(emailRef)}
      />
    </>
  );
}

7. Ref и useState - когда что использовать

// Используй useState если нужен re-render
function Counter1() {
  const [count, setCount] = useState(0);
  // Обновление count -> re-render компонента
  return <div>{count}</div>;
}

// Используй useRef если re-render не нужен
function Stopwatch() {
  const timeRef = useRef<number>(0);
  
  const handleStart = () => {
    // Обновляем timeRef без re-render
    timeRef.current = Date.now();
  };
  
  // Компонент не перерендерится
  return <button onClick={handleStart}>Старт</button>;
}

// Комбинируй оба подхода
function Form() {
  const [formData, setFormData] = useState({ name: '', email: '' });
  const submitCountRef = useRef<number>(0);
  
  const handleSubmit = () => {
    submitCountRef.current += 1; // Не вызывает re-render
    console.log(`Попыток отправки: ${submitCountRef.current}`);
    
    setFormData({ name: '', email: '' }); // Вызывает re-render
  };
  
  return (
    <>
      <input
        value={formData.name}
        onChange={(e) => setFormData({ ...formData, name: e.target.value })}
      />
      <button onClick={handleSubmit}>Отправить</button>
    </>
  );
}

8. Обновление ref в custom hook

function useTimer(initialTime: number = 0) {
  const timeRef = useRef<number>(initialTime);
  const intervalRef = useRef<number | null>(null);
  const [time, setTime] = useState(initialTime);

  const start = useCallback(() => {
    if (intervalRef.current === null) {
      intervalRef.current = window.setInterval(() => {
        timeRef.current += 1;
        setTime(timeRef.current);
      }, 1000);
    }
  }, []);

  const stop = useCallback(() => {
    if (intervalRef.current !== null) {
      clearInterval(intervalRef.current);
      intervalRef.current = null;
    }
  }, []);

  const reset = useCallback(() => {
    stop();
    timeRef.current = initialTime;
    setTime(initialTime);
  }, [initialTime, stop]);

  return { time, start, stop, reset };
}

// Использование
function Stopwatch() {
  const { time, start, stop, reset } = useTimer(0);

  return (
    <>
      <p>Время: {time}s</p>
      <button onClick={start}>Старт</button>
      <button onClick={stop}>Стоп</button>
      <button onClick={reset}>Сброс</button>
    </>
  );
}

9. Обновление ref с помощью Object.assign

function ComplexComponent() {
  const dataRef = useRef<{ name: string; count: number }>({
    name: 'Initial',
    count: 0,
  });

  const updateRef = (updates: Partial<typeof dataRef.current>) => {
    // Обновляем объект в ref
    Object.assign(dataRef.current, updates);
    console.log(dataRef.current);
  };

  return (
    <>
      <button onClick={() => updateRef({ name: 'Updated' })}>
        Update Name
      </button>
      <button onClick={() => updateRef({ count: dataRef.current.count + 1 })}>
        Increment Count
      </button>
    </>
  );
}

Важные правила

  1. Не читай ref в render методе - используй useState если нужны updates в UI
  2. Не присваивай ref вне ref callback'а - используй useRef или forwardRef
  3. Обновляй ref через .current - это основной способ
  4. Очищай ref в useEffect cleanup - для предотвращения утечек памяти
  5. Используй useImperativeHandle для API компонента - если компонент нужно "контролировать" снаружи

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

  • Управление фокусом на input'ах
  • Триггеринг анимаций
  • Интеграция с third-party DOM библиотеками
  • Управление медиа элементами (video, audio)
  • Хранение таймеров и интервалов

Когда НЕ использовать Ref

  • Для управления состоянием - используй useState
  • Для обновления UI - используй setState
  • Когда можно решить через props
Как обновить ref? | PrepBro