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

Как useRef используется с useImperativeHandle?

2.0 Middle🔥 221 комментариев
#JavaScript Core

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

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

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

useRef и useImperativeHandle в React

Основные концепции

useRef — хук для создания мутабельного объекта, который живёт столько же, сколько компонент. Обычно используется для доступа к DOM элементам.

useImperativeHandle — хук для экспортирования функций/значений из компонента наружу, сделав компонент "управляемым" извне.

Вместе они позволяют родителю вызывать методы на дочернем компоненте.

Простой пример: Фокус на input

// Дочерний компонент
const TextInput = React.forwardRef((props, ref) => {
  const inputRef = useRef(null);
  
  // Экспортируем метод focus для использования родителем
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    clear: () => {
      inputRef.current.value = "";
    },
  }));
  
  return <input ref={inputRef} type="text" />;
});

// Родительский компонент
function App() {
  const inputRef = useRef(null);
  
  const handleFocus = () => {
    inputRef.current.focus(); // Вызовем метод из дочернего компонента
  };
  
  const handleClear = () => {
    inputRef.current.clear();
  };
  
  return (
    <div>
      <TextInput ref={inputRef} />
      <button onClick={handleFocus}>Focus Input</button>
      <button onClick={handleClear}>Clear Input</button>
    </div>
  );
}

Ключевые компоненты

1. forwardRef (передача ref в дочерний компонент)

By default, refs не передаются как props. Нужно использовать forwardRef:

// Без forwardRef — ref не будет работать
function MyInput(props) {
  return <input />; // props.ref будет undefined
}

// С forwardRef — ref передаётся вторым аргументом
const MyInput = forwardRef((props, ref) => {
  return <input ref={ref} />;
});

2. useRef (создание изменяемого объекта)

const ref = useRef(initialValue);

// ref — это объект с одним свойством .current
console.log(ref.current); // может быть DOM элемент, число, объект и т.д.

// useRef НЕ запускает ре-рендер при изменении
ref.current = "new value"; // не вызовет re-render

3. useImperativeHandle (экспорт API)

useImperativeHandle(ref, createHandle, dependencies);

// ref — ref от родителя
// createHandle — функция, которая возвращает объект с методами
// dependencies — зависимости (как в useEffect)

useImperativeHandle(
  ref,
  () => ({
    // методы и значения для родителя
    focus: () => { /* ... */ },
    value: () => { /* ... */ },
    reset: () => { /* ... */ },
  }),
  [] // пересчитать, если что-то из зависимостей изменилось
);

Практический пример: видеоплеер

const VideoPlayer = forwardRef(({ src }, ref) => {
  const videoRef = useRef(null);
  const [isPlaying, setIsPlaying] = useState(false);
  
  useImperativeHandle(ref, () => ({
    play: () => {
      videoRef.current.play();
      setIsPlaying(true);
    },
    pause: () => {
      videoRef.current.pause();
      setIsPlaying(false);
    },
    getCurrentTime: () => videoRef.current.currentTime,
    setCurrentTime: (time) => {
      videoRef.current.currentTime = time;
    },
    getDuration: () => videoRef.current.duration,
    isPlaying: () => isPlaying,
  }));
  
  return (
    <video
      ref={videoRef}
      src={src}
      onPlay={() => setIsPlaying(true)}
      onPause={() => setIsPlaying(false)}
    />
  );
});

// Использование
function MovieApp() {
  const playerRef = useRef(null);
  
  return (
    <div>
      <VideoPlayer ref={playerRef} src="movie.mp4" />
      <button onClick={() => playerRef.current.play()}>Play</button>
      <button onClick={() => playerRef.current.pause()}>Pause</button>
      <button onClick={() => alert(playerRef.current.getCurrentTime())}>Show Time</button>
    </div>
  );
}

Пример: модальное окно

const Modal = forwardRef(({ children }, ref) => {
  const [isOpen, setIsOpen] = useState(false);
  const dialogRef = useRef(null);
  
  useImperativeHandle(ref, () => ({
    open: () => {
      setIsOpen(true);
      dialogRef.current?.showModal();
    },
    close: () => {
      setIsOpen(false);
      dialogRef.current?.close();
    },
    isOpen: () => isOpen,
  }));
  
  return (
    <dialog ref={dialogRef}>
      {children}
      <button onClick={() => ref.current.close()}>Close</button>
    </dialog>
  );
});

// Использование
function App() {
  const modalRef = useRef(null);
  
  return (
    <div>
      <button onClick={() => modalRef.current?.open()}>Open Modal</button>
      <Modal ref={modalRef}>
        <h2>Important Message</h2>
        <p>This is a modal dialog</p>
      </Modal>
    </div>
  );
}

Пример: форма с валидацией

const Form = forwardRef(({ onSubmit }, ref) => {
  const emailRef = useRef(null);
  const passwordRef = useRef(null);
  const [errors, setErrors] = useState({});
  
  useImperativeHandle(ref, () => ({
    validate: () => {
      const newErrors = {};
      
      if (!emailRef.current.value.includes("@")) {
        newErrors.email = "Invalid email";
      }
      if (passwordRef.current.value.length < 6) {
        newErrors.password = "Password too short";
      }
      
      setErrors(newErrors);
      return Object.keys(newErrors).length === 0;
    },
    getValues: () => ({
      email: emailRef.current.value,
      password: passwordRef.current.value,
    }),
    reset: () => {
      emailRef.current.value = "";
      passwordRef.current.value = "";
      setErrors({});
    },
  }));
  
  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      if (ref.current.validate()) {
        onSubmit(ref.current.getValues());
      }
    }}>
      <input ref={emailRef} type="email" placeholder="Email" />
      {errors.email && <span>{errors.email}</span>}
      
      <input ref={passwordRef} type="password" placeholder="Password" />
      {errors.password && <span>{errors.password}</span>}
      
      <button type="submit">Sign In</button>
    </form>
  );
});

// Использование
function LoginPage() {
  const formRef = useRef(null);
  
  const handleLogin = async (credentials) => {
    const response = await fetch("/api/login", {
      method: "POST",
      body: JSON.stringify(credentials),
    });
    
    if (response.ok) {
      formRef.current?.reset();
    }
  };
  
  return <Form ref={formRef} onSubmit={handleLogin} />;
}

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

// Плохо: использовать для prop-based управления
const BadComponent = forwardRef(({ isOpen }, ref) => {
  useImperativeHandle(ref, () => ({
    open: () => { /* ... */ }, // Зачем? isOpen prop уже это делает
  }));
});

// Хорошо: пропс для такого случая
const GoodComponent = ({ isOpen, onClose }) => {
  // Используй pропсы для управления состоянием
};

Лучшие практики

  1. Используй только когда нужно управлять DOM — звоните по методам на компонентах, это не React way
  2. Ограничивай API — экспортируй только необходимые методы
  3. Типизируй с TypeScript:
interface TextInputHandle {
  focus: () => void;
  clear: () => void;
}

const TextInput = forwardRef<TextInputHandle>((props, ref) => {
  // ...
});
  1. Избегай abuse — если можно решить через props и state, решай через них
  2. Документируй экспортируемый API — какие методы доступны, что они делают

Выводы: useImperativeHandle — мощный инструмент для создания управляемых компонентов, но использовать его нужно осторожно. Обычно достаточно props и state.

Как useRef используется с useImperativeHandle? | PrepBro