为什么我在使用react-router时必须手动刷新页面才能让组件加载?
Why do I have to manually refresh the page for the component to load when using react-router?
我有一个 React 应用程序,它使用 react-router-dom 从侧边栏加载不同的组件。每当我点击边栏中的 link 时,URL 就会发生变化,但我必须手动刷新页面才能加载页面的实际内容。
我希望我的应用程序在单击边栏 link 时自动刷新页面,而不是用户必须手动刷新页面才能加载组件。
function App() {
const [locale, setLocale] = useState('en');
return (
<>
<Routes>
<Route path="/" element={<IntlProvider locale={locale}><Layout setLocale={setLocale} /></IntlProvider>}>
<Route index element={<Home />} />
<Route path="experience" element={<Experience />} />
<Route path="skills" element={<Skills />} />
<Route path="portfolio" element={<Portfolio />} />
<Route path="contact" element={<Contact />} />
</Route>
</Routes>
</>
);
}
export default App;
这是我的App.js
const Sidebar = ({ toggled, handleToggleSidebar }) => {
const intl = useIntl();
return (
<ProSidebar
toggled={toggled}
breakPoint="md"
onToggle={handleToggleSidebar}
>
<SidebarHeader>
<div
style={{
padding: '24px',
textTransform: 'uppercase',
fontWeight: 'bold',
fontSize: 14,
letterSpacing: '1px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
<Link style={{ display: 'block', padding: '15px 0' }} to="/">
<img style={{ display: 'block', margin: '-3em 3em', width: '133px', height: 'auto' }} src={LogoS} alt="Logo" />
</Link>
</div>
</SidebarHeader>
<SidebarContent>
<Menu iconShape="circle">
<MenuItem icon={<FaHome size={32} />}>
{intl.formatMessage({ id: 'Home' })}
<NavLink activeclassname="active" to="/"></NavLink>
</MenuItem>
<MenuItem icon={<FaProjectDiagram size={32} />}>
{intl.formatMessage({ id: 'Experience' })}
<NavLink to="/experience"></NavLink>
</MenuItem>
<MenuItem icon={<FaToolbox size={32} />}>
{intl.formatMessage({ id: 'Skills' })}
<NavLink to="/skills"></NavLink>
</MenuItem>
<MenuItem icon={<FaFolderOpen size={32} />}>
{intl.formatMessage({ id: 'Portfolio' })}
<NavLink to="/portfolio"></NavLink>
</MenuItem>
<MenuItem icon={<FaTelegramPlane size={32} />}>
{intl.formatMessage({ id: 'Contact' })}
<NavLink to="/contact"></NavLink>
</MenuItem>
</Menu>
</SidebarContent>
</ProSidebar >
);
};
export default Sidebar;
这是我的Sidebar.js
function Layout({ setLocale }) {
const [rtl, setRtl] = useState(false);
const [collapsed, setCollapsed] = useState(false);
const [image, setImage] = useState(true);
const [toggled, setToggled] = useState(false);
const handleCollapsedChange = (checked) => {
setCollapsed(checked);
};
const handleRtlChange = (checked) => {
setRtl(checked);
setLocale(checked ? 'ar' : 'en');
};
const handleImageChange = (checked) => {
setImage(checked);
};
const handleToggleSidebar = (value) => {
setToggled(value);
};
return (
<div className={`app ${rtl ? 'rtl' : ''} ${toggled ? 'toggled' : ''}`}>
<Sidebar
image={image}
collapsed={collapsed}
rtl={rtl}
toggled={toggled}
handleToggleSidebar={handleToggleSidebar}
/>
<Main
image={image}
toggled={toggled}
collapsed={collapsed}
rtl={rtl}
handleToggleSidebar={handleToggleSidebar}
handleCollapsedChange={handleCollapsedChange}
handleRtlChange={handleRtlChange}
handleImageChange={handleImageChange}
/>
<Outlet />
</div>
);
}
export default Layout;
这是呈现页面的 Layout.js。
const Contact = () => {
const [letterClass, setLetterClass] = useState('text-animate')
const form = useRef()
useEffect(() => {
return setTimeout(() => {
setLetterClass('text-animate-hover')
}, 3000)
}, [])
const sendEmail = (e) => {
e.preventDefault()
emailjs
.sendForm(
'service_v8uv1al',
'template_xge6tgj',
form.current,
'UuX3z3S-mWAnAL7BY')
.then(
() => {
alert('Message successfully sent!')
window.location.reload(false)
},
() => {
alert('Failed to send the message, please try again')
}
)
}
return (
<>
<div className="container contact-page">
<div className="text-zone">
<h1>
<AnimatedLetters
letterClass={letterClass}
strArray={['C', 'o', 'n', 't', 'a', 'c', 't', ' ', 'm', 'e']}
idx={15}
/>
</h1>
<div className='app_footer-cards'>
<div className='app_footer-card'>
<img src={emailLogo} alt="email" />
<a href="mailto:k.maumau11@gmail.com" className='p-text'>k.maumau0@gmail.com</a>
</div>
<div className='app_footer-card'>
<img src={phoneLogo} alt="mobile" />
<a href="tel: +1 (832) 764-9796" className='p-text'>+1 (832) 764-9796</a>
</div>
</div>
<p>
If you have a request or question, or simply just want to
say Hello don't hesitate to contact me using the form below!
</p>
<div className="contact-form">
<form ref={form} onSubmit={sendEmail}>
<ul>
<li className="half">
<input placeholder="Name" type="text" name="name" required />
</li>
<li className="half">
<input
placeholder="Email"
type="email"
name="email"
required
/>
</li>
<li>
<input
placeholder="Subject"
type="text"
name="subject"
required
/>
</li>
<li>
<textarea
placeholder="Message"
name="message"
required
></textarea>
</li>
<li>
<input type="submit" className="flat-button" value="SEND" />
</li>
</ul>
</form>
</div>
</div>
</div>
<Loader type="ball-scale" />
</>
)
}
export default Contact
联系页面代码
问题
您的应用程序代码在离开页面时实际上崩溃了。目前尚不清楚为什么您没有看到 React 错误页面(也许您是 运行 生产构建 ),但问题是对于每个页面您都使用 useEffect
挂钩设置超时以更新某些本地状态。
useEffect(() => {
return setTimeout(() => {
setLetterClass('text-animate-hover');
}, 3000);
}, []);
这个问题是 React 假设 anything returned from the useEffect
hook 是一个清理函数,当组件被调用时rerendering/unmounting。 setTimeout
returns 代表计时器 ID 的数字。
重新加载页面实际上是在当前 URL 路径上重新加载整个应用程序。
解决方案
在每个页面上,将逻辑重构为 return 清除超时的清理函数。
useEffect(() => {
const timer = setTimeout(() => {
setLetterClass('text-animate-hover');
}, 3000);
return () => clearTimeout(timer);
}, []);
这允许在组件安装时实例化超时,并且对于用户离开页面并且卸载该组件的偶然机会,它将清除超时并且不会调用将排队的回调状态状态。
建议
AnimatedLetters
组件的每个页面似乎都具有相同的动画 letterClass
状态。与其在所有页面上复制相同的 code/logic,不如将其抽象为自定义 React 挂钩,该挂钩 return 是 letterClass
值。
示例:
const useLetterClass = ({
start = 'text-animate',
end = 'text-animate-hover'
}) => {
const [letterClass, setLetterClass] = useState(start);
useEffect(() => {
const timer = setTimeout(() => {
setLetterClass(end);
}, 3000);
return () => clearTimeout(timer);
}, []);
return { letterClass };
};
在每个页面组件中导入 useLetterClass
挂钩、调用并传递 returned letterClass
属性值。
示例:
const Contact = () => {
const { letterClass } = useLetterClass({
start: 'text-animate',
end: 'text-animate-hover',
});
const form = useRef();
const sendEmail = (e) => {
...
}
return (
<>
<div className="container contact-page">
<div className="text-zone">
<h1>
<AnimatedLetters
letterClass={letterClass}
strArray={['C', 'o', 'n', 't', 'a', 'c', 't', ' ', 'm', 'e']}
idx={15}
/>
</h1>
...
</div>
</div>
<Loader type="ball-scale" />
</>
);
};
您提供的沙箱代码似乎也存在与 SCSS 的 some 相关的问题,特别是缺少 $sidebar-color
变量。我对 SCSS 不太熟悉,但我不怀疑它与我上面描述的 routing/navigation 问题有关。
我希望我的应用程序在单击边栏 link 时自动刷新页面,而不是用户必须手动刷新页面才能加载组件。
function App() {
const [locale, setLocale] = useState('en');
return (
<>
<Routes>
<Route path="/" element={<IntlProvider locale={locale}><Layout setLocale={setLocale} /></IntlProvider>}>
<Route index element={<Home />} />
<Route path="experience" element={<Experience />} />
<Route path="skills" element={<Skills />} />
<Route path="portfolio" element={<Portfolio />} />
<Route path="contact" element={<Contact />} />
</Route>
</Routes>
</>
);
}
export default App;
这是我的App.js
const Sidebar = ({ toggled, handleToggleSidebar }) => {
const intl = useIntl();
return (
<ProSidebar
toggled={toggled}
breakPoint="md"
onToggle={handleToggleSidebar}
>
<SidebarHeader>
<div
style={{
padding: '24px',
textTransform: 'uppercase',
fontWeight: 'bold',
fontSize: 14,
letterSpacing: '1px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
<Link style={{ display: 'block', padding: '15px 0' }} to="/">
<img style={{ display: 'block', margin: '-3em 3em', width: '133px', height: 'auto' }} src={LogoS} alt="Logo" />
</Link>
</div>
</SidebarHeader>
<SidebarContent>
<Menu iconShape="circle">
<MenuItem icon={<FaHome size={32} />}>
{intl.formatMessage({ id: 'Home' })}
<NavLink activeclassname="active" to="/"></NavLink>
</MenuItem>
<MenuItem icon={<FaProjectDiagram size={32} />}>
{intl.formatMessage({ id: 'Experience' })}
<NavLink to="/experience"></NavLink>
</MenuItem>
<MenuItem icon={<FaToolbox size={32} />}>
{intl.formatMessage({ id: 'Skills' })}
<NavLink to="/skills"></NavLink>
</MenuItem>
<MenuItem icon={<FaFolderOpen size={32} />}>
{intl.formatMessage({ id: 'Portfolio' })}
<NavLink to="/portfolio"></NavLink>
</MenuItem>
<MenuItem icon={<FaTelegramPlane size={32} />}>
{intl.formatMessage({ id: 'Contact' })}
<NavLink to="/contact"></NavLink>
</MenuItem>
</Menu>
</SidebarContent>
</ProSidebar >
);
};
export default Sidebar;
这是我的Sidebar.js
function Layout({ setLocale }) {
const [rtl, setRtl] = useState(false);
const [collapsed, setCollapsed] = useState(false);
const [image, setImage] = useState(true);
const [toggled, setToggled] = useState(false);
const handleCollapsedChange = (checked) => {
setCollapsed(checked);
};
const handleRtlChange = (checked) => {
setRtl(checked);
setLocale(checked ? 'ar' : 'en');
};
const handleImageChange = (checked) => {
setImage(checked);
};
const handleToggleSidebar = (value) => {
setToggled(value);
};
return (
<div className={`app ${rtl ? 'rtl' : ''} ${toggled ? 'toggled' : ''}`}>
<Sidebar
image={image}
collapsed={collapsed}
rtl={rtl}
toggled={toggled}
handleToggleSidebar={handleToggleSidebar}
/>
<Main
image={image}
toggled={toggled}
collapsed={collapsed}
rtl={rtl}
handleToggleSidebar={handleToggleSidebar}
handleCollapsedChange={handleCollapsedChange}
handleRtlChange={handleRtlChange}
handleImageChange={handleImageChange}
/>
<Outlet />
</div>
);
}
export default Layout;
这是呈现页面的 Layout.js。
const Contact = () => {
const [letterClass, setLetterClass] = useState('text-animate')
const form = useRef()
useEffect(() => {
return setTimeout(() => {
setLetterClass('text-animate-hover')
}, 3000)
}, [])
const sendEmail = (e) => {
e.preventDefault()
emailjs
.sendForm(
'service_v8uv1al',
'template_xge6tgj',
form.current,
'UuX3z3S-mWAnAL7BY')
.then(
() => {
alert('Message successfully sent!')
window.location.reload(false)
},
() => {
alert('Failed to send the message, please try again')
}
)
}
return (
<>
<div className="container contact-page">
<div className="text-zone">
<h1>
<AnimatedLetters
letterClass={letterClass}
strArray={['C', 'o', 'n', 't', 'a', 'c', 't', ' ', 'm', 'e']}
idx={15}
/>
</h1>
<div className='app_footer-cards'>
<div className='app_footer-card'>
<img src={emailLogo} alt="email" />
<a href="mailto:k.maumau11@gmail.com" className='p-text'>k.maumau0@gmail.com</a>
</div>
<div className='app_footer-card'>
<img src={phoneLogo} alt="mobile" />
<a href="tel: +1 (832) 764-9796" className='p-text'>+1 (832) 764-9796</a>
</div>
</div>
<p>
If you have a request or question, or simply just want to
say Hello don't hesitate to contact me using the form below!
</p>
<div className="contact-form">
<form ref={form} onSubmit={sendEmail}>
<ul>
<li className="half">
<input placeholder="Name" type="text" name="name" required />
</li>
<li className="half">
<input
placeholder="Email"
type="email"
name="email"
required
/>
</li>
<li>
<input
placeholder="Subject"
type="text"
name="subject"
required
/>
</li>
<li>
<textarea
placeholder="Message"
name="message"
required
></textarea>
</li>
<li>
<input type="submit" className="flat-button" value="SEND" />
</li>
</ul>
</form>
</div>
</div>
</div>
<Loader type="ball-scale" />
</>
)
}
export default Contact
联系页面代码
问题
您的应用程序代码在离开页面时实际上崩溃了。目前尚不清楚为什么您没有看到 React 错误页面(也许您是 运行 生产构建 ),但问题是对于每个页面您都使用 useEffect
挂钩设置超时以更新某些本地状态。
useEffect(() => {
return setTimeout(() => {
setLetterClass('text-animate-hover');
}, 3000);
}, []);
这个问题是 React 假设 anything returned from the useEffect
hook 是一个清理函数,当组件被调用时rerendering/unmounting。 setTimeout
returns 代表计时器 ID 的数字。
重新加载页面实际上是在当前 URL 路径上重新加载整个应用程序。
解决方案
在每个页面上,将逻辑重构为 return 清除超时的清理函数。
useEffect(() => {
const timer = setTimeout(() => {
setLetterClass('text-animate-hover');
}, 3000);
return () => clearTimeout(timer);
}, []);
这允许在组件安装时实例化超时,并且对于用户离开页面并且卸载该组件的偶然机会,它将清除超时并且不会调用将排队的回调状态状态。
建议
AnimatedLetters
组件的每个页面似乎都具有相同的动画 letterClass
状态。与其在所有页面上复制相同的 code/logic,不如将其抽象为自定义 React 挂钩,该挂钩 return 是 letterClass
值。
示例:
const useLetterClass = ({
start = 'text-animate',
end = 'text-animate-hover'
}) => {
const [letterClass, setLetterClass] = useState(start);
useEffect(() => {
const timer = setTimeout(() => {
setLetterClass(end);
}, 3000);
return () => clearTimeout(timer);
}, []);
return { letterClass };
};
在每个页面组件中导入 useLetterClass
挂钩、调用并传递 returned letterClass
属性值。
示例:
const Contact = () => {
const { letterClass } = useLetterClass({
start: 'text-animate',
end: 'text-animate-hover',
});
const form = useRef();
const sendEmail = (e) => {
...
}
return (
<>
<div className="container contact-page">
<div className="text-zone">
<h1>
<AnimatedLetters
letterClass={letterClass}
strArray={['C', 'o', 'n', 't', 'a', 'c', 't', ' ', 'm', 'e']}
idx={15}
/>
</h1>
...
</div>
</div>
<Loader type="ball-scale" />
</>
);
};
您提供的沙箱代码似乎也存在与 SCSS 的 some 相关的问题,特别是缺少 $sidebar-color
变量。我对 SCSS 不太熟悉,但我不怀疑它与我上面描述的 routing/navigation 问题有关。