NextJs构建时React Hooks条件调用报错原因及解决方法咨询
错误信息
./pages/genre.js/[genre].js 15:14 错误:React Hook "useFetchTrendingCatagory" 被条件调用。React Hooks必须在每次组件渲染时按完全相同的顺序调用。您是否在提前return后意外调用了React Hook?react-hooks/rules-of-hooks 17:14 错误:React Hook "useFetchTopRatedCatagory" 被条件调用。React Hooks必须在每次组件渲染时按完全相同的顺序调用。您是否在提前return后意外调用了React Hook?react-hooks/rules-of-hooks 19:14 错误:React Hook "useFetchMovieGenreResults" 被条件调用。React Hooks必须在每次组件渲染时按完全相同的顺序调用。您是否在提前return后意外调用了React Hook?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"; 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> ) }
错误原因
违反了React Hooks的核心规则:所有Hooks必须在组件/自定义Hook的顶层按固定顺序调用,不能在条件判断、循环、嵌套函数中调用。
你的useMovies自定义Hook里,根据genre的不同值会调用不同的Hook,这导致组件每次渲染时,调用的Hook数量和顺序可能不一致——比如当genre是Trending时,只会调用useFetchTrendingCatagory,不会调用另外两个Hook;当genre切换为其他值时,调用的Hook又会变化。这种条件调用Hook的行为,会破坏React对Hook状态的追踪逻辑,因此被ESLint的react-hooks/rules-of-hooks规则拦截(开发环境可能没严格校验,但构建阶段会触发错误)。
另外注意代码里的小错误:case 'Top Rated"': 多了一个多余的双引号,需要修正为case 'Top Rated':。
解决办法
方法1:在组件顶层调用所有Hook,再根据genre选择结果
直接在Genre组件的顶层调用所有需要的Hook,确保每次渲染时Hook的调用顺序完全一致,之后再根据genre的值选择对应的数据:
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选择对应的数据 const mymovies = genre === 'Trending' ? trendingMovies : genre === 'Top Rated' ? topRatedMovies : genreMovies; return ( <div> {/* <Navbar /> */} <div>{genre}</div> <Moviegenreresults movies={mymovies} /> </div> ) }
方法2:修改自定义Hook,先顶层调用所有Hook再返回结果
如果想保留useMovies这个自定义Hook,可以在其内部先顶层调用所有需要的Hook,再根据genre返回对应结果:
import React from "react"; import { useRouter } from "next/router"; import useFetchMovieGenreResults from "../../hooks/useFetchMovieGenreResults"; import useFetchTrendingCatagory from "../../hooks/useFetchTrendingCatagory"; import useFetchTopRatedCatagory from "../../hooks/useFetchTopRatedCatagory"; const useMovies = (genre) => { // 先在顶层调用所有Hook,确保顺序一致 const trendingMovies = useFetchTrendingCatagory(); const topRatedMovies = useFetchTopRatedCatagory(); const genreMovies = useFetchMovieGenreResults(genre); switch (genre) { case 'Trending': return trendingMovies; case 'Top Rated': // 修正多余的双引号 return topRatedMovies; default: return genreMovies; } } 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> ) }
方法3:在Hook内部添加条件逻辑,避免无效请求
如果担心调用所有Hook会触发不必要的API请求,可以修改每个Hook,添加一个控制是否执行请求的参数,在不需要请求时跳过数据获取:
比如修改useFetchTrendingCatagory:
// hooks/useFetchTrendingCatagory.js import { useState, useEffect } from 'react'; export default function useFetchTrendingCatagory(shouldFetch = true) { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { if (!shouldFetch) return; // 不满足条件则跳过请求 const fetchData = async () => { setLoading(true); try { const res = await fetch('你的趋势电影API地址'); const result = await res.json(); setData(result); } catch (err) { setError(err.message); } finally { setLoading(false); } }; fetchData(); }, [shouldFetch]); return { data, loading, error }; }
同理修改useFetchTopRatedCatagory和useFetchMovieGenreResults,然后在useMovies中调用时传入条件参数:
const useMovies = (genre) => { const isTrending = genre === 'Trending'; const isTopRated = genre === 'Top Rated'; // 根据条件控制是否执行请求 const trendingMovies = useFetchTrendingCatagory(isTrending); const topRatedMovies = useFetchTopRatedCatagory(isTopRated); const genreMovies = useFetchMovieGenreResults(genre, !isTrending && !isTopRated); switch (genre) { case 'Trending': return trendingMovies; case 'Top Rated': return topRatedMovies; default: return genreMovies; } }
内容的提问来源于stack exchange,提问作者Time2learnagain

