React实现Unsplash式路由弹窗功能的方案咨询
实现Unsplash式路由弹窗的React方案
核心逻辑
要实现的效果拆解:
- 首页点击图片:路由切换为详情路径(
/photo/:id),同时在首页上方弹出详情弹窗,首页内容保留 - 关闭弹窗:路由切回首页(
/),弹窗消失 - 弹窗打开时刷新:直接跳转至独立的真实详情页
核心思路是利用React Router的嵌套路由区分两种场景:从首页跳转的路由渲染弹窗(嵌套在首页内),直接访问的路由渲染独立详情页。
React Router v6 具体实现
1. 路由配置
在根组件中配置两个层级的路由:嵌套路由用于首页弹窗,独立路由用于真实详情页
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import Home from './Home'; import PhotoModal from './PhotoModal'; import PhotoDetailPage from './PhotoDetailPage'; function App() { return ( <Router> <Routes> {/* 首页路由,嵌套详情弹窗路由 */} <Route path="/" element={<Home />}> <Route path="photo/:id" element={<PhotoModal />} /> </Route> {/* 独立详情页路由,直接访问/刷新时触发 */} <Route path="/photo/:id" element={<PhotoDetailPage />} /> </Routes> </Router> ); } export default App;
2. 首页组件(Home)
处理图片点击跳转逻辑,通过Outlet渲染嵌套的弹窗组件
import { useNavigate, Outlet } from 'react-router-dom'; import { photos } from './data'; // 假设这是你的图片数据 function Home() { const navigate = useNavigate(); // 打开弹窗:跳转到嵌套路由并标记来源为首页 const openModal = (photoId) => { navigate(`/photo/${photoId}`, { state: { isModal: true } }); }; return ( <div className="home-container"> {/* 首页图片网格 */} <div className="photo-grid"> {photos.map(photo => ( <div key={photo.id} className="photo-item" onClick={() => openModal(photo.id)} > <img src={photo.thumbnail} alt={photo.title} /> </div> ))} </div> {/* 嵌套路由出口:用于渲染弹窗 */} <Outlet /> </div> ); } export default Home;
3. 弹窗组件(PhotoModal)
处理弹窗显示/关闭逻辑,同时判断是否为直接访问路由,若是则跳转到独立详情页
import { useParams, useNavigate, useLocation } from 'react-router-dom'; import { useEffect } from 'react'; import { photos } from './data'; function PhotoModal() { const { id } = useParams(); const navigate = useNavigate(); const location = useLocation(); const photo = photos.find(item => item.id === id); // 刷新时state丢失,自动跳转到独立详情页 useEffect(() => { if (!location.state?.isModal) { navigate(`/photo/${id}`, { replace: true }); } }, [id, navigate, location.state]); // 关闭弹窗:切回首页路由 const closeModal = () => { navigate('/'); }; if (!photo) return null; return ( <div className="modal-overlay" onClick={closeModal}> <div className="modal-content" onClick={e => e.stopPropagation()}> <button className="close-btn" onClick={closeModal}>×</button> <img src={photo.largeUrl} alt={photo.title} /> <h3>{photo.title}</h3> <p>{photo.description}</p> </div> </div> ); } export default PhotoModal;
4. 独立详情页组件(PhotoDetailPage)
直接访问/刷新时渲染的真实详情页
import { useParams, useNavigate } from 'react-router-dom'; import { photos } from './data'; function PhotoDetailPage() { const { id } = useParams(); const navigate = useNavigate(); const photo = photos.find(item => item.id === id); if (!photo) return <div>图片不存在</div>; return ( <div className="detail-page"> <button className="back-btn" onClick={() => navigate('/')}>返回首页</button> <img src={photo.largeUrl} alt={photo.title} /> <h1>{photo.title}</h1> <p>{photo.description}</p> {/* 其他详情内容 */} </div> ); } export default PhotoDetailPage;
关键逻辑说明
- 路由区分:嵌套路由
/photo/:id依赖首页路由/,只有从首页跳转时才会渲染弹窗;独立路由/photo/:id会直接渲染详情页 - 状态标记:跳转时通过
state: { isModal: true }标记路由来源,刷新时state丢失,弹窗组件会自动跳转到独立详情页 - 后退处理:点击浏览器后退按钮,路由从
/photo/:id切回/,弹窗自动消失,符合用户习惯
React Router v5 适配方案
如果使用v5,调整路由和组件逻辑如下:
路由配置
import { BrowserRouter, Switch, Route } from 'react-router-dom'; function App() { return ( <BrowserRouter> <Switch> <Route path="/" component={Home}> <Route path="photo/:id" component={PhotoModal} /> </Route> <Route path="/photo/:id" component={PhotoDetailPage} /> </Switch> </BrowserRouter> ); }
首页组件(Home)
import { useHistory, useRouteMatch } from 'react-router-dom'; function Home() { const history = useHistory(); const match = useRouteMatch(); const openModal = (photoId) => { history.push({ pathname: `${match.url}/photo/${photoId}`, state: { isModal: true } }); }; return ( <div className="home-container"> {/* 图片网格内容 */} {/* 渲染子路由弹窗 */} {match.isExact ? null : this.props.children} </div> ); }
弹窗和详情页组件逻辑与v6基本一致,仅将useNavigate替换为useHistory即可。
内容的提问来源于stack exchange,提问作者Fani
相关产品推荐
相关产品推荐

