使用 React Router 6 和 Remix 实现 Modals

2025-06-09

使用 React Router 6 和 Remix 实现 Modals

2010 年代,Pinterest、Instagram 等应用开始流行。模态框通常用作一种“详细信息”视图,用于聚焦集合中的特定对象(例如 Pinterest 图板),同时又不会完全脱离父页面的上下文。

模态 Gif

React Router 6 Repository 上有一个示例,介绍如何使用statebackgroundLocation保持父页面可见并在顶部显示模式来实现此模式。

如何使用 React Router Data Routers (^6.4.0) 和 Remix 实现同样的效果?让我们来一探究竟!

首先,当使用时,loaders我们将无法使用相同的设置机制,state.backgroundLocation因为在我们定义路线时路线状态不存在:

// We can't access state here!
const router = createBrowserRouter([
  {
    path: '/',
    element: <App />,
    children: []
  }
)]
Enter fullscreen mode Exit fullscreen mode

本次尝试的源代码在这里

因此,带有数据加载器的模态框有两种选择,根据您的使用情况,它们都有各自的优缺点:

使用嵌套路由的模态框

第一个选项是使用 React Router 的嵌套路由功能来启用可以在其父路由内显示的模式:

让我们使用 React Router 仓库中的相同示例。我们希望每当用户点击图片时显示一个模态框,并将图片详情视图放在模态框内,而不是跳转到新的页面。

路由配置:

const router = createBrowserRouter([
  {
    path: '/',
    element: <App />,
    children: [
      {
        element: <Layout />,
        children: [
          {
            index: true,
            element: <Home />,
          },
          {
            path: 'img/:id',
            element: <ImageView />,
          },
          {
            path: 'gallery',
            element: <Gallery />,
            children: [
              {
                path: 'img/:id',
                element: <Modal />,
              },
            ],
          },
          {
            path: '*',
            element: <NoMatch />,
          },
        ],
      },
    ],
  },
]);
Enter fullscreen mode Exit fullscreen mode

Modal组件现在是组件内的嵌套路由Gallery

在 Remix 中,只需创建一个新的文件夹结构即可创建嵌套路由

routes 
| index.tsx 
| gallery   
    | index.tsx
    | modal.tsx
Enter fullscreen mode Exit fullscreen mode

下一步,我们需要OutletGallery组件添加一个来渲染模态框

export function Gallery() {

  return (
    <div style={{ padding: '0 24px' }}>
      <h2>Gallery</h2>
     ...
      <Outlet />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

优点:

  • 模态框可以使用自己的loaders:如果您的模态框需要访问数据获取,则可以为它们分配自己的加载器,以将它们与父路由分开并独立工作。

  • 它不需要大量的设置:它们只是在父路由之上呈现的嵌套路由。

  • 持久导航:由于模式只是常规路线,因此您可以引用它们,使用 URL 路径打开和关闭它们。

缺点:

  • 模态框需要在路由下配置,这意味着无法在根/URL中呈现它们

  • 模态框只会显示在已定义的嵌套路由内,无法从任何页面访问。如果要在其他路径上显示它们,则需要反复手动创建这些路由。

  • 如果您想移动模式,则必须重构使用它的整个路线段。

  • 有时很难保持背景页面的上下文,因为出口必须位于正确的位置。

链接到完整代码示例

使用搜索参数的模态框

嵌套路由的替代方法是使用 URL 中的搜索参数(使用平台!💪)

要打开包含图库中图片的模式,我们需要导航到以下 URL 路径:

?modal-type=gallery-img&gallery-img-id=1

然后我们可以在应用程序的根加载器中访问 URL Search Params:

export const loader: LoaderFunction = ({ request }) => {
  const queryParams = new URL(request.url).searchParams;
  const modalType = queryParams.get('modal-type');
  const galleryImgId = queryParams.get('gallery-img-id');
  if (modalType === 'gallery-img') {
    if (!galleryImgId) {
      console.error(
        "must pass the gallery-img-id param if you want to render the 'gallery-img' modal"
      );
      return null;
    }
    return {
      modalType: 'gallery-img',
      galleryImgId,
    };
  }
  return null;
};
Enter fullscreen mode Exit fullscreen mode

然后在应用程序根的渲染函数中,我们检查模态类型和参数。

export default function App() {
  const modalProps = useLoaderData() as ModalProps;
  return (
    <div>
      <h1>Modal Example</h1>
      <Modal modalProps={modalProps || null} />
      <Outlet />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

在组件内部Modal,我们可以检查modalType并根据该值渲染不同的模态框和内容。您可以
使用简单的界面和条件渲染功能注册并创建任意数量的模态框。

优点:

  • 模态框是全局的;您可以在根 URL 路径中使用它们/,并且可以从应用程序中的任何位置打开它们。

  • 易于重构并在应用程序内移动。
    它们还支持持久导航,并使用 URL 管理其可见性状态。

缺点:

  • 这些模态框不能有自己的加载器,因此它们要么需要在渲染函数(渲染然后获取)中请求数据,要么从父级传递数据作为道具。

  • 它们必须使用模态渲染引擎或实用程序在应用程序的根目录中“注册”,从长远来看,这可能更难维护。

  • 根目录中的搜索参数会触发所有其他加载器,这可能会导致重新渲染和性能问题。shouldRevalidate()如果您不想重新运行加载器,一个潜在的解决方案是引入。

源代码链接

结论

一种模式并不比另一种更好;两者都有优点和缺点,这取决于您的使用情况。

如果您需要您的模态框有自己的加载器,那么请使用嵌套路由方法,但请记住它们不是很灵活且易于重构。

如果您想创建可以从任何地方打开并且更灵活的“全局模式”,则可以使用搜索参数方法;但是,请注意应用程序根中的紧密耦合以及不使用加载器对性能的影响。

 致谢:

Jon制作了一个关于如何使用搜索参数创建模态框的视频,并且还提供了一个包含完整示例的存储库

和往常一样, Remix 团队的Matt总是乐于在 Discord 上回答这类问题。

鏂囩珷鏉ユ簮锛�https://dev.to/infoxicator/modals-with-react-router-6-and-remix-1e35
PREV
将颜色提升到新的 (CSS) 级别
NEXT
如何在 Discord 服务器上获取 GitHub 通知。GenAI 直播!| 2025 年 6 月 4 日