Next.js+Typescript项目添加Loader触发Invalid hook call错误如何解决
问题排查和修复方案
1. 触发Invalid hook call报错的直接原因
你在Index.tsx中把useLoading、useRouter、useEffect全部写在了Home函数组件的外部,React 明确规定Hook只能在函数组件的顶层作用域或者自定义Hook内部调用,不能在组件外的全局作用域调用。
2. 其他需要修复的逻辑问题
2.1 路由事件解绑逻辑错误
你给router.events.off传入的是新创建的匿名函数,和on阶段传入的回调不是同一个引用,会导致事件解绑失败,出现内存泄漏。
2.2 路由事件监听不全
仅监听routeChangeStart事件无法覆盖所有路由跳转场景,还需要补充监听routeChangeComplete(路由跳转成功)和routeChangeError(路由跳转失败)事件来结束加载状态,否则跳转完成后Loader不会消失。
2.3 自定义Hook错误提示文本错误
useLoading中的报错提示写的是useCount must be used within a CountProvider,和实际的Hook、Provider名称不符,排查问题时会造成误导。
2.4 缺少Provider全局包裹
没有把整个Next.js应用包裹在LoadingProvider中,哪怕修复了Hook调用位置的问题,也会触发useLoading的抛出错误。
2.5 Loader组件依赖缺失
Loader.tsx缺少NextPage类型导入和样式文件导入,会触发类型报错和样式找不到的问题。
修复后的完整代码
修复后 Index.tsx
import Loader from "../components/Loader/Loader"; import { useLoading } from "../context/LoadingContext"; import { useRouter } from "next/router"; import { useEffect } from "react"; import type { NextPage } from "next"; const Home: NextPage = () => { // 所有Hook移到组件内部顶层调用 const { state, dispatch } = useLoading(); const router = useRouter(); useEffect(() => { // 统一声明回调,保证绑定和解绑用的是同一个函数引用 const handleStart = () => dispatch({ type: "startLoading" }); const handleEnd = () => dispatch({ type: "endLoading" }); const handleError = () => dispatch({ type: "endLoading" }); router.events.on("routeChangeStart", handleStart); router.events.on("routeChangeComplete", handleEnd); router.events.on("routeChangeError", handleError); return () => { router.events.off("routeChangeStart", handleStart); router.events.off("routeChangeComplete", handleEnd); router.events.off("routeChangeError", handleError); }; }, [dispatch, router.events]); useEffect(() => { // 初始页面加载完成后关闭加载状态 dispatch({ type: "endLoading" }); }, [dispatch]); return ( <> {state.loading ? ( <Loader /> ) : ( <> <div> {/* 其他业务组件 */} </div> </> )} </> ); }; export default Home;
修复后 LoadingContext.ts 错误提示部分
export function useLoading() { const context = React.useContext(LoadingContext); if (context === undefined) { // 修正错误提示文本 throw new Error("useLoading must be used within a LoadingProvider"); } return context; }
新增 pages/_app.tsx 全局包裹Provider
import type { AppProps } from "next/app"; import LoadingProvider from "../context/LoadingContext"; export default function App({ Component, pageProps }: AppProps) { return ( <LoadingProvider> <Component {...pageProps} /> </LoadingProvider> ); }
修复后 Loader.tsx
import type { NextPage } from "next"; import styles from './Loader.module.css' // 替换为你的实际样式文件路径 const Loader: NextPage = () => { return ( <div className={styles.loader_wrapper}> <div className={styles.content}> <span className={styles.loading_text}>Loading...</span> </div> </div> ); }; export default Loader;
内容的提问来源于stack exchange,提问作者hanu
相关产品推荐
相关产品推荐

