You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React自定义Hook中布局重渲染后如何正确传递useRef?

React组件区域全屏功能的ref传递问题

问题背景

需要实现React组件中某一区域的全屏切换功能(类似YouTube视频全屏),通过组件角落的按钮切换状态。组件依赖React Context传递的数据,需等待数据加载完成后才会渲染包含目标DOM元素的完整组件。封装了自定义Hook useFullScreen 实现全屏逻辑,但遇到ref无法正确传递给Hook的问题——Hook始终获取不到viewerRef对应的DOM元素,尽管在组件的useEffect中能正常拿到该ref。

现有代码

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> Loading page</h4>
    }

 return (
   <div id="view-workspace" ref={viewerRef}>
     <button onClick={toggleFullScreen as () => void}>
      {isFullScreen ? 'Exit Fullscreen' : 'Enter Fullscreen'}
     </button> 
     <div> {/* Rest of the page goes here */} </div>
   </div>
)
};

问题原因

核心问题是闭包陷阱:enterFullScreen 和 toggleFullScreen 函数在Hook初始化时创建,此时ref.current为null(因为组件还在加载状态,目标DOM未渲染)。后续数据加载完成、DOM渲染后,ref.current更新为真实DOM元素,但这些函数的闭包中捕获的仍是初始的ref引用(current为null),导致调用enterFullScreen时无法获取到最新的DOM元素。

另外,组件中用let声明viewerRef不符合React最佳实践,应该用const,因为useRef返回的是稳定的引用,不需要重新赋值。

解决方案

1. 优化自定义Hook,确保函数能获取最新的ref

使用React.useCallback包裹enterFullScreen和toggleFullScreen,将ref加入依赖数组,这样每次ref变化时,函数会重新创建,从而捕获最新的ref.current。

修改后的useFullScreen.tsx:

import * as React from "react";

type useFullScreenProps = {
    ref: React.MutableRefObject<null | HTMLDivElement>;
};

const useFullScreen = ({ref} : useFullScreenProps) => {
    const [isFullScreen, setIsFullScreen] = React.useState(false);
  
    // 用useCallback包裹,依赖ref确保获取最新的current
    const enterFullScreen = React.useCallback(() => {
        if (ref.current) {
            ref.current.requestFullscreen();
        }
    }, [ref]);

    const exitFullScreen = React.useCallback(() => {
        if (document.fullscreenElement) {
            document.exitFullscreen();
        }
    }, []);

    // 依赖enterFullScreen和isFullScreen,确保切换逻辑用最新的函数
    const toggleFullScreen = React.useCallback(() => {
        isFullScreen ? exitFullScreen() : enterFullScreen();
    }, [isFullScreen, enterFullScreen, exitFullScreen]);

    React.useEffect(() => {
        const handleFullscreenChange = () => {
            setIsFullScreen(document.fullscreenElement !== null);
        };

        document.addEventListener("fullscreenchange", handleFullscreenChange);

        return () => document.removeEventListener("fullscreenchange", handleFullscreenChange);
    }, []);

    return [isFullScreen, toggleFullScreen] as const;
};

export default useFullScreen;

2. 调整组件中的ref声明和Hook调用

将let viewerRef改为const,并移除不必要的类型断言:

修改后的ViewerComponent.tsx:

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> {/* Rest of the page goes here */} </div>
   </div>
)
};

额外优化:处理全屏API的兼容性(可选)

如果需要兼容更多浏览器,可以在enterFullScreen中添加前缀兼容:

const enterFullScreen = React.useCallback(() => {
    const element = ref.current;
    if (!element) return;
    
    if (element.requestFullscreen) {
        element.requestFullscreen();
    } else if ((element as any).webkitRequestFullscreen) {
        (element as any).webkitRequestFullscreen();
    } else if ((element as any).mozRequestFullScreen) {
        (element as any).mozRequestFullScreen();
    } else if ((element as any).msRequestFullscreen) {
        (element as any).msRequestFullscreen();
    }
}, [ref]);

内容的提问来源于stack exchange,提问作者Showcaselfloyd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 21:14:52