Next.js构建报错:useRouter及自定义Hook调用违反规则
初始问题描述
开发Next.js应用时,开发环境正常,但执行构建出现报错:
./pages/genre.js/[genre].js
13:18 Error: React Hook "useRouter" is called in function "genre" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use". react-hooks/rules-of-hooks
相关代码如下:
import React from "react"; import { useRouter } from "next/router"; import useFetchMovieGenreResults from "../../hooks/useFetchMovieGenreResults"; import { useState } from "react"; import useFetchTrendingCatagory from "../../hooks/useFetchTrendingCatagory"; import useFetchTopRatedCatagory from "../../hooks/useFetchTopRatedCatagory"; export default function genre() { const router = useRouter(); const { genre } = router.query; if (genre == "Trending") { let mymovies = useFetchTrendingCatagory(); return ( <div> {/* <Navbar /> */} <div>{genre}</div> <Moviegenreresults movies={mymovies} /> </div> ); } else if (genre == "Top Rated") { let mymovies = useFetchTopRatedCatagory(); return ( <div> {/* <Navbar /> */} <div>{genre}</div> <Moviegenreresults movies={mymovies} /> </div> ); } else { let mymovies = useFetchMovieGenreResults(genre); return ( <div> {/* <Navbar /> */} <div>{genre}</div> <Moviegenreresults movies={mymovies} /> </div> ); }
初始报错原因
- 组件命名不符合规范:React要求函数组件名称必须以大写字母开头,你的组件命名为
genre(小写开头),ESLint规则判定它不是合法的React组件,因此在其中调用useRouterHook违反了Hooks使用规则。 - Hooks被条件调用:在
if/else分支中分别调用不同的自定义Hook,违反了React Hooks核心规则——Hooks必须在组件顶层调用,不能在条件语句、循环、嵌套函数中调用,确保每次渲染时Hooks的调用顺序完全一致。
初始问题解决方向
- 修正组件名称为大写开头的
Genre; - 将所有Hook调用移到组件顶层,避免条件性调用。
更新后的问题描述
尝试修改后代码如下:
import React from "react"; import { useRouter } from "next/router"; import useFetchMovieGenreResults from "../../hooks/useFetchMovieGenreResults"; import { useState } from "react"; import useFetchTrendingCatagory from "../../hooks/useFetchTrendingCatagory"; import useFetchTopRatedCatagory from "../../hooks/useFetchTopRatedCatagory"; const useMovies = (genre) => { switch (genre) { case 'Trending': return useFetchTrendingCatagory() case 'Top Rated"': return useFetchTopRatedCatagory() default: return useFetchMovieGenreResults(genre) } } export default function Genre () { const router = useRouter(); const { genre } = router.query; const mymovies = useMovies(genre) return ( <div> {/* <Navbar /> */} <div>{genre}</div> <Moviegenreresults movies={mymovies} /> </div> ) }
但构建仍报错:
./pages/genre.js/[genre].js
15:14 Error: React Hook "useFetchTrendingCatagory" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return? react-hooks/rules-of-hooks
17:14 Error: React Hook "useFetchTopRatedCatagory" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return? react-hooks/rules-of-hooks
19:14 Error: React Hook "useFetchMovieGenreResults" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return? react-hooks/rules-of-hooks
更新后报错原因
是的,switch语句属于条件判断逻辑,在其中调用Hook依然违反了Hooks调用规则——不管是if/else还是switch,只要是条件分支里的Hook调用,都会导致不同渲染周期中Hook的调用顺序不一致,React无法正确追踪Hook状态。
最终解决办法
必须确保所有自定义Hook都在顶层作用域调用,然后根据genre的值选择使用对应的结果,而不是条件性调用Hook。具体实现如下:
import React from "react"; import { useRouter } from "next/router"; import useFetchMovieGenreResults from "../../hooks/useFetchMovieGenreResults"; import useFetchTrendingCatagory from "../../hooks/useFetchTrendingCatagory"; import useFetchTopRatedCatagory from "../../hooks/useFetchTopRatedCatagory"; export default function Genre () { const router = useRouter(); const { genre } = router.query; // 所有Hook都在组件顶层调用,确保每次渲染顺序一致 const trendingMovies = useFetchTrendingCatagory(); const topRatedMovies = useFetchTopRatedCatagory(); const genreMovies = useFetchMovieGenreResults(genre); // 根据genre选择对应的电影数据 let mymovies; if (genre === "Trending") { mymovies = trendingMovies; } else if (genre === "Top Rated") { mymovies = topRatedMovies; } else { mymovies = genreMovies; } return ( <div> {/* <Navbar /> */} <div>{genre}</div> <Moviegenreresults movies={mymovies} /> </div> ) }
补充说明
- 这种方式虽然会同时调用三个自定义Hook,但如果你的Hook内部做了缓存(比如用
useSWR、react-query或自定义缓存逻辑),不会重复发起无效请求; - 若担心不必要的请求,可以在自定义Hook内部根据传入参数判断是否执行请求逻辑,比如
useFetchMovieGenreResults可在genre为空时不发起请求。
内容的提问来源于stack exchange,提问作者Time2learnagain

