React中window load事件不生效,如何实现页面加载器?
问题分析与解决方案
为什么你的Loader组件里的window.load事件没触发?
window的load事件是在页面所有资源(图片、样式表、脚本等)加载完成后才会触发。而React组件的useEffect(空依赖数组)是在组件挂载到DOM之后才执行的——如果页面的资源在React完成初始化、Loader组件挂载之前就已经加载完毕,这时候再添加load事件监听器,自然不会触发。
你对React渲染机制的误解
虽然你把Loader组件放在App.jsx的最前面导入,但React的组件渲染流程是:
- React框架本身先初始化
- 开始渲染组件树,把Loader组件挂载到DOM
- 执行Loader组件的
useEffect回调
这个流程的启动时机是在DOMContentLoaded事件之后(也就是HTML解析完成后),但window.load事件是等待所有资源加载完成,所以如果页面资源加载速度快于React初始化+组件挂载的速度,load事件会在useEffect执行前就已经触发,导致监听器无法捕获到事件。
实现页面加载器的替代方案
方案1:在index.html中提前渲染Loader,资源加载完成后再显示React内容
这是最可靠的方式,Loader会在页面一开始就显示,不需要等待React初始化:
<!-- index.html --> <!DOCTYPE html> <html lang="zh-CN"> <head> <!-- 你的meta、样式等 --> <link rel="stylesheet" href="./loader.sass"> <!-- 提前引入Loader样式 --> </head> <body> <!-- 先渲染Loader --> <div id="loader"> <div class="center"> <div class="rect"></div> <div class="rect"></div> </div> </div> <!-- React根容器默认隐藏 --> <div id="root" style="display: none;"></div> <script> // 监听页面所有资源加载完成 window.addEventListener('load', () => { // 隐藏Loader document.getElementById('loader').style.display = 'none'; // 显示React根容器并渲染应用 document.getElementById('root').style.display = 'block'; ReactDOM.render(<App />, document.getElementById('root')); }); </script> </body> </html>
这样能确保Loader在所有资源加载完成前一直显示,完全覆盖你的需求。
方案2:检查document.readyState,兼容事件已触发的情况
如果一定要在React组件里实现,可以先检查当前文档状态,避免错过已经触发的load事件:
import React, { useState, useEffect } from "react" import './loader.sass' const Loader = () => { const [isPageLoaded, setIsPageLoaded] = useState(false); useEffect(() => { const handlePageLoad = () => setIsPageLoaded(true); // 如果文档已经加载完成,直接触发回调 if (document.readyState === 'complete') { handlePageLoad(); } else { // 否则添加监听器,记得清理 window.addEventListener('load', handlePageLoad); return () => window.removeEventListener('load', handlePageLoad); } }, []); // 加载完成后不渲染Loader if (isPageLoaded) return null; return ( <div id="loader"> <div className="center"> <div className="rect"></div> <div className="rect"></div> </div> </div> ) } export default Loader
方案3:手动监听特定资源加载(适合需要等待部分资源的场景)
如果不需要等待所有资源,只需要等待特定的图片、样式,可以用Promise手动监听:
import React, { useState, useEffect } from "react" import './loader.sass' const Loader = () => { const [isLoaded, setIsLoaded] = useState(false); useEffect(() => { const resourcePromises = []; // 监听所有img标签的加载 document.querySelectorAll('img').forEach(img => { if (!img.complete) { resourcePromises.push( new Promise(resolve => { img.onload = resolve; img.onerror = resolve; // 加载失败也算完成 }) ); } }); // 监听样式表加载 document.querySelectorAll('link[rel="stylesheet"]').forEach(link => { if (!link.sheet) { resourcePromises.push( new Promise(resolve => { link.onload = resolve; link.onerror = resolve; }) ); } }); // 所有资源加载完成后隐藏Loader Promise.all(resourcePromises).then(() => setIsLoaded(true)); }, []); if (isLoaded) return null; return ( <div id="loader"> <div className="center"> <div className="rect"></div> <div className="rect"></div> </div> </div> ) } export default Loader
内容的提问来源于stack exchange,提问作者Yukichi
相关产品推荐
相关产品推荐

