React Router 6预加载器实现报错解决及可行方案
解决React Router 6中预加载器(Preloader)的Hook调用错误及实现方案
问题背景
你想给个人网站添加预加载器,要求首次访问任意页面时先显示加载过渡效果,但当前代码因为在函数组件外部直接使用useState和useEffect Hooks,触发了错误:Warning: Invalid hook call. Hooks can only be called inside of the body of a function component.
错误原因
React Hooks(包括useState、useEffect)必须在函数组件的顶层作用域内调用,不能放在组件外部、条件判断、循环等非组件顶层的位置。你的代码把Hook写在了全局作用域,完全违反了Hook的使用规则。
基础修复方案:封装成根组件
把预加载逻辑封装到一个函数组件内部,在组件里合法使用Hook,再控制预加载器和路由的渲染:
import React from 'react' import ReactDOM from 'react-dom/client' import './index.css' import { createBrowserRouter, RouterProvider } from "react-router-dom"; import { useState, useEffect } from 'react'; // 导入组件和页面 import Header from './Header' import ErrorPage from "./error-page"; import Home from './Home' import Profile from "./Profile" import Works from "./Works" import Blogs from "./Blogs" import Wm from "./works/web-monitization" import Ss from "./works/scheduling-system" import Bs from "./works/bharatSteel" import Load from "./loader" // 封装根组件,处理预加载逻辑 function AppRoot() { // 初始设为true,确保页面加载时立刻显示预加载器 const [screenLoading, setScreenLoading] = useState(true); useEffect(() => { // 定时器控制预加载时长,可根据实际需求调整 const loadTimer = setTimeout(() => { setScreenLoading(false); }, 1000); // 清理定时器,避免内存泄漏 return () => clearTimeout(loadTimer); }, []); const router = createBrowserRouter([ { path: "/", element: <Header />, errorElement: <ErrorPage />, children: [ { path: "", element: <Home />, index: true }, { path: "/profile", element: <Profile /> }, { path: "/works", element: <Works /> }, { path: "/works/web-monetization", element: <Wm /> }, { path: "works/input-validaton", element: <Wm /> }, { path: "works/scheduling-system", element: <Ss proji="scheduling-system" /> }, { path: "works/bharat-steel", element: <Bs proji="scheduling-system" /> }, { path: "blogs", element: <Blogs /> }, { path: "loader", element: <Load /> } ] } ]); return ( <div className='layout'> {screenLoading ? <Load /> : <RouterProvider router={router} />} </div> ); } ReactDOM.createRoot(document.getElementById('root')).render(<AppRoot />)
修复说明
- 把所有逻辑放到
AppRoot函数组件内部,符合Hook的使用规则 - 初始
screenLoading设为true,保证页面启动时直接显示预加载器 - 增加定时器清理逻辑,防止组件卸载时出现内存泄漏
进阶实现:结合React Router 6的Loader实现页面级加载
如果需要在路由切换时也显示加载状态(不止首次加载),可以结合React Router 6的loader和useNavigation钩子实现全局路由加载:
1. 全局加载组件
创建一个监听路由导航状态的组件:
// GlobalLoader.jsx import { useNavigation } from 'react-router-dom'; import Load from './loader'; export default function GlobalLoader() { const navigation = useNavigation(); // 判断路由是否正在加载数据 const isLoading = navigation.state === "loading"; return isLoading ? <Load /> : null; }
2. 集成到根路由布局
修改根路由的布局,把全局加载组件加入:
// 修改路由配置中的根element { path: "/", element: ( <div className='layout'> <Header /> <GlobalLoader /> <Outlet /> {/* 渲染子路由内容 */} </div> ), errorElement: <ErrorPage />, children: [/* 子路由配置不变 */] }
3. 给需要加载的路由添加Loader
比如给首页添加异步Loader模拟数据加载:
{ path: "", element: <Home />, index: true, loader: async () => { // 模拟API请求延迟 await new Promise(resolve => setTimeout(resolve, 1000)); // 返回页面需要的数据 return { data: "首页数据" }; } }
这样,每当用户切换到带有Loader的路由时,全局加载组件会自动显示,直到Loader执行完成。
额外优化建议
- 不要依赖固定定时器:实际项目中,建议等待关键资源(API数据、图片)加载完成后再关闭预加载器,而不是用固定时长
- 优化过渡效果:给预加载器添加CSS过渡动画,让切换更平滑
- 避免重复加载:如果只需要首次显示预加载器,可以用
localStorage标记是否完成首次加载,防止刷新后重复显示
内容的提问来源于stack exchange,提问作者Achint vishwas
相关产品推荐
相关产品推荐

