Вопрос или проблема
Итак, это довольно сложно.
У меня есть компонент React, и клиент спросил, можем ли мы сделать одну часть страницы на весь экран (как видео на YouTube в полноэкранном режиме). У меня есть небольшая кнопка в углу компонента, которую пользователь может использовать для переключения этой части компонента на полноэкранный режим.
Проблема в том, что компонент нуждается в данных, которые он получает из другого источника, и они передаются через объект контента React. Ему нужны эти данные до загрузки страницы.
Я подумал, что имеет смысл создать пользовательский хук для этого.
Мой хук выглядит так.
useFullScreen.tsx
import * as React from "react";
type useFullScreenProps = {
ref: React.MutableRefObject<null | HTMLDivElement>;
};
const useFullScreen = ({ref} : useFullScreenProps) => {
const [isFullScreen, setIsFullScreen] = React.useState(false);
const enterFullScreen = () => {
if (ref.current) {
ref.current.requestFullscreen();
}
};
const exitFullScreen = () => {
if (document.fullscreenElement) {
document.exitFullscreen();
}
};
const toggleFullScreen = () => {
isFullScreen ? exitFullScreen() : enterFullScreen();
};
React.useEffect(() => {
const handleFullscreenChange = () => {
setIsFullScreen(document.fullscreenElement !== null);
};
document.addEventListener("fullscreenchange", handleFullscreenChange);
return () => document.removeEventListener("fullscreenchange", handleFullscreenChange);
}, []);
return [isFullScreen, toggleFullScreen];
};
export default useFullScreen;
Компонент просмотра вызывает его так.
ViewerComponent.tsx
import useFullScreen from "./Common/useFullScreen";
const ViewerComponent = () => {
let viewerRef = React.useRef<HTMLDivElement>(null);
const [isFullScreen, toggleFullScreen] = useFullScreen(viewerRef as any);
const viewData = useClientObject<ExamData>(React.useContext(ClientContext), "viewer_data");
if (viewData === undefined || Object.keys(viewData).length === 0) {
return <h4> Загрузка страницы</h4>
}
return (
<div id="view-workspace" ref={viewerRef}>
<button onClick={toggleFullScreen as () => void}>
{isFullScreen ? 'Выйти из полноэкранного режима' : 'Войти в полноэкранный режим'}
</button>
<div> {/* Остальная часть страницы идёт здесь */} </div>
<div>
)
};
Итак, проблема в том, что я не могу понять, как передать ref в хук, чтобы он мог его использовать.
Поскольку моя страница использует данные, которые ей нужны, прежде чем она сможет отобразить весь компонент. Она никогда не видит DOM-элемент viewerRef
.
Я попытался сделать всё, с помощью useEffect я правильно получаю ссылку на элемент DOM, но потом не знаю, как снова вызвать хук?
React.useEffect(()=>{
console.log(viewerRef.current) <-- это работает, но как мне сообщить хуку об этом
},
[examData])
Предложение: Используйте { ref: viewerRef }
в качестве параметра при передаче в useFullScreen
.
Вот изменённый код:
const [isFullScreen, toggleFullScreen] = useFullScreen({ ref: viewerRef });
Основное изменение здесь заключается в том, чтобы оставить div
, на который указывает viewerRef
(т.е. <div id="view-workspace" ref={viewerRef}>
) в DOM, независимо от того, завершилась ли загрузка данных. Таким образом, даже если данные не загрузились во время начального рендера, viewerRef.current
всё равно может указывать на этот div
элемент.
Не рекомендуется кастовать viewerRef
в any
перед передачей его в useFullScreen
.
Кастование в any
ослабляет проверку типов, что может привести к тому, что ошибки при передаче неправильных параметров не будут обнаружены быстро.
Например, если useFullScreen
ожидает объект в качестве параметра, но использует any
для обхода проверки типов, TypeScript не выдаст ошибку.
Если useFullScreen
в будущем потребует больше параметров, этот метод прямой передачи также не будет легко расширять.
.
Ответ или решение
Как использовать хук useRef внутри собственного хука после повторной отрисовки в React
При решении задачи, связанной с полноэкранным режимом в компоненте React, важно корректно работать с референсами DOM-элементов и состоянием. Ваша цель — это сделать так, чтобы компонент, который должен быть полноэкранным, имел возможность доступа к своему DOM-элементу вне зависимости от состояния загрузки данных. Так как вы работаете с пользовательским хуком useFullScreen
, который требует референса к DOM-элементу, необходимо правильно организовать передачу референса в ваш хук.
Проблема с референсами
В вашем текущем решении референс viewerRef
создаётся, но вы столкнулись с тем, что при первом рендере, когда данные ещё не загружены, референс указывает на null
, что препятствует корректной работе хука useFullScreen
. Однако вы можете обойти эту проблему, обеспечив наличие референса к контейнеру вашего компонента уже на этапе первого рендера.
Решение проблемы
-
Используйте простой референс: Убедитесь, что референс используется с текущим элементом DOM даже до того, как данные будут загружены. Это позволит вашему хуку работать корректно.
-
Передайте референс правильно: Вместо того чтобы осуществлять приведение
viewerRef
к типуany
, передайте его в хук как часть объекта с ключом, таким какref
. Это не только улучшит читаемость кода, но и увеличит безопасность типизации.
Пример исправленного кода
Вот как можно модифицировать вызов хука в вашем компоненте ViewerComponent
:
import React from "react";
import useFullScreen from "./Common/useFullScreen";
const ViewerComponent = () => {
const viewerRef = React.useRef<HTMLDivElement>(null);
const [isFullScreen, toggleFullScreen] = useFullScreen({ ref: viewerRef });
const viewData = useClientObject<ExamData>(React.useContext(ClientContext), "viewer_data");
if (viewData === undefined || Object.keys(viewData).length === 0) {
return <h4>Loading page</h4>;
}
return (
<div id="view-workspace" ref={viewerRef}>
<button onClick={toggleFullScreen}>
{isFullScreen ? 'Exit Fullscreen' : 'Enter Fullscreen'}
</button>
<div>
{/* Остальная часть страницы */}
</div>
</div>
);
};
Объяснение кода
- Передача референса в хук: Теперь вы передаёте
viewerRef
вuseFullScreen
как часть объекта. Это позволяет вашему хукуuseFullScreen
использовать референс правильно, даже если данные ещё не готовы к отображению. - Безопасность типизации: Избегая использования
any
, вы сохраняете преимущества, которые предоставляет TypeScript, такие как проверка типов и избежание ошибок во время компиляции.
Управление состоянием полноэкранного режима
const useFullScreen = ({ ref }: useFullScreenProps) => {
const [isFullScreen, setIsFullScreen] = React.useState(false);
const enterFullScreen = () => {
if (ref.current) {
ref.current.requestFullscreen();
}
};
const exitFullScreen = () => {
if (document.fullscreenElement) {
document.exitFullscreen();
}
};
const toggleFullScreen = () => {
isFullScreen ? exitFullScreen() : enterFullScreen();
};
React.useEffect(() => {
const handleFullscreenChange = () => {
setIsFullScreen(document.fullscreenElement !== null);
};
document.addEventListener("fullscreenchange", handleFullscreenChange);
return () => {
document.removeEventListener("fullscreenchange", handleFullscreenChange);
};
}, []);
return [isFullScreen, toggleFullScreen];
};
Заключение
Обратите внимание на то, что корректное использование референсов и управление состоянием в пользовательских хуках — это основа успешной работы компонентов в React. Соблюдение принципов типизации TypeScript также способствует повышению безопасности кода и упрощает его сопровождение.
Этот подход поможет вам гарантировать, что ваш компонент будет работать корректно, даже если данные ещё не загружены, что является важным аспектом для создания отзывчивого интерфейса пользователя.