使用useNavigate跳转时useEffect未触发、useState初始值异常问题
React SpinnerLoading组件路由跳转不显示问题排查与解决
组件代码与使用方式
SpinnerLoading组件代码
import { useEffect, useState } from "react"; import PropagateLoader from "react-spinners/PropagateLoader"; import { motion } from "framer-motion"; const SpinnerLoading = ({ children }) => { const [loading, setLoading] = useState(true); console.log(loading); // 第一处console.log useEffect(() => { const timeout = setTimeout(() => setLoading(false), 1000); console.log(loading); // 第二处console.log return () => clearTimeout(timeout); }, []); return ( <> {loading ? ( <PropagateLoader color="#36d7b7" cssOverride={{ display: "block", position: "fixed", top: "50%", left: "50%", transform: "translate(-50%, -50%)", }} /> ) : ( <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }}> {children} </motion.div> )} </> ); }; export default SpinnerLoading;
路由使用方式
<Route path="/" element={<SpinnerLoading><Homepage /></SpinnerLoading>} />
问题描述
- 手动刷新页面时,Spinner正常显示;但通过
useNavigate编程式跳转至该页面时,子组件直接加载,Spinner完全不显示。 - 添加的两处
console.log表现异常:手动刷新时两处都会触发;编程式跳转时仅第一处触发,且输出值为false,第二处(useEffect内)完全没执行。
原因解析
- React Router的组件复用机制:使用
useNavigate跳转时,React Router会复用已经挂载在DOM中的组件实例(包括SpinnerLoading),不会重新创建新的组件实例。而空依赖数组的useEffect只会在组件首次挂载时执行一次,复用阶段不会再次触发。 - 状态留存问题:组件复用时,内部的state会保留上一次的状态值。第一次进入页面后
loading会被设为false,当再次跳转回来时,loading的初始值不会重新设为true,而是直接沿用之前的false状态,所以直接渲染子组件,Spinner不显示。
解决方案
通过监听路由变化,在每次进入该页面时重置loading状态,确保Spinner能正常触发。修改后的组件代码如下:
import { useEffect, useState } from "react"; import PropagateLoader from "react-spinners/PropagateLoader"; import { motion } from "framer-motion"; import { useLocation } from "react-router-dom"; const SpinnerLoading = ({ children }) => { const [loading, setLoading] = useState(true); const location = useLocation(); useEffect(() => { // 每次路由进入时重置loading为true setLoading(true); const timeout = setTimeout(() => setLoading(false), 1000); // 组件卸载或路由变化时清除定时器,避免内存泄漏 return () => clearTimeout(timeout); }, [location.pathname]); // 依赖路由pathname,路由变化时重新执行effect return ( <> {loading ? ( <PropagateLoader color="#36d7b7" cssOverride={{ display: "block", position: "fixed", top: "50%", left: "50%", transform: "translate(-50%, -50%)", }} /> ) : ( <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }}> {children} </motion.div> )} </> ); }; export default SpinnerLoading;
修改说明
- 引入
useLocation钩子获取当前路由的路径信息; - 将
useEffect的依赖改为location.pathname,这样每次路由切换到该页面时,都会重新执行effect; - 在effect开头先将
loading设为true,确保进入页面时优先显示Spinner,再在定时器结束后切换为子组件。
内容的提问来源于stack exchange,提问作者khoaoaoaoa
相关产品推荐
相关产品推荐

