You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;

关键逻辑说明

  1. 路由区分:嵌套路由/photo/:id依赖首页路由/,只有从首页跳转时才会渲染弹窗;独立路由/photo/:id会直接渲染详情页
  2. 状态标记:跳转时通过state: { isModal: true }标记路由来源,刷新时state丢失,弹窗组件会自动跳转到独立详情页
  3. 后退处理:点击浏览器后退按钮,路由从/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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 22:44:57