掌握 React Router:构建你的 React 应用导航

大家好,我是长林啊!一个 Go、Rust 爱好者,同时也是一名全栈开发者;致力于终生学习和技术分享。

本文首发于微信公众号《全栈修炼之旅》,欢迎大家关注!

在构建现代 Web 应用时,导航是连接用户界面的关键纽带。React Router 作为 React 生态中的核心路由库,为开发者提供了强大的工具来实现 SPA(单页应用)的导航逻辑。它不仅简化了页面间的跳转,还支持动态路由匹配、懒加载和状态管理集成,让应用的导航更加灵活和高效。

初识 React Router

React Router 是一个用于 React 应用程序的路由库,它允许你以声明式的方式来定义应用的导航结构。

介绍 React Router的重要性

React Router 的重要性在于它为构建单页应用(SPA)提供了一个强大而灵活的导航解决方案。以下是 React Router 的几个关键重要性点:

  • 用户体验:React Router 允许应用在不重新加载页面的情况下进行页面跳转,提供了无缝的用户体验。
  • 应用结构:它帮助开发者以组件化的方式组织应用的视图,使得应用的结构更加清晰和模块化。
  • 动态路由:React Router 支持动态路由,可以根据URL参数动态渲染组件,这在处理用户输入和API数据时非常有用。
  • 导航控制:提供了编程式导航和声明式导航的方式,使得开发者可以更灵活地控制应用的导航流程。
  • 状态同步:React Router 能够与 React 的状态管理库(如Redux或Context API)集成,同步路由状态与应用状态。
  • 性能优化:通过懒加载和代码分割,React Router 有助于提高应用的加载速度和运行效率。
  • SEO友好:对于需要进行搜索引擎优化的应用,React Router 支持服务器端渲染,有助于提高SEO效果。
  • 社区支持:React Router 有着庞大的社区支持,提供了大量的教程、插件和第三方集成方案。
  • 安全性:React Router 提供了路由保护机制,可以防止未授权的路由访问,增强应用的安全性。
  • 跨平台兼容性:React Router 不仅限于 Web 应用,还可以与 React Native 等其他React平台集成,提供跨平台的导航解决方案。

    概述 React Router 在现代Web应用中的作用

  • 增强用户体验:通过实现无缝页面跳转,React Router提升了用户交互的流畅性。
  • 促进代码组织:它通过组件化路由,帮助开发者以模块化的方式组织代码,提高应用的可维护性。
  • 支持动态内容:React Router允许根据URL动态加载内容,为构建数据驱动的应用提供了便利。
  • 提高性能:通过懒加载和代码分割,它有助于减少初始加载时间和提高应用性能。
  • 保障安全性:提供了路由保护功能,确保应用的导航逻辑安全且符合业务规则。
  • 改善SEO:支持服务器端渲染,有助于提高应用的搜索引擎优化效果。
  • 灵活集成:React Router可以与多种状态管理和UI库集成,提供一致的开发体验。

下文有不少示例演示,我们就用 vite 创建一个新的 React 项目吧!创建的命令如下:

$ npm create vite@latest react-router-tutorial -- --template react

创建完成之后,用自己熟悉的 IDE 工具打开,并在终端中运行命令启动项目。

安装 React Router

可以选择自己熟悉的 Node.js 包管理工具,建议在同一个项目中只使用一种包管理工具,混合使用可能会导致一些包依赖出问题;建议直接使用 pnpm 包管理工具。

$ pnpm add react-router-dom

路由

React Router 提供了多种创建路由的方式,在 v6.4 又引入了4中新的创建路由的方式:

  • createBrowserRouter 它使用 DOM History API 来更新 URL 并管理历史记录堆栈。
  • createMemoryRouter
  • createHashRouter
  • createStaticRouter
    当然原来声明式的创建路由的方式仍然还是保留了:

  • 这四种声明式的创建路由不支持 react-router 新增的一些 Data 相关的 API 的使用,官方也建议所有 Web 项目使用 createBrowserRouter 的方式创建路由。

创建路由的方法详解

createBrowserRouter 的使用

它还支持 v6.4 数据 API,如 loaders、actions、fetchers 等。

const router = createBrowserRouter([
  {
    path: "/",
    element: ,
    loader: rootLoader,
    children: [
      {
        path: "team",
        element: ,
        loader: teamLoader,
      },
    ],
  },
]);

createBrowserRouter 的类型:

function createBrowserRouter (
    routes: RouteObject[],
    opts?: {
        basename?: string;
        future?: FutureConfig;
        hydrationData?: HydrationState;
        window?: Window;
    }
): RemixRouter;
  • routes:Route 对象的数组,在 children 属性上有嵌套路由。
  • basename:应用程序的基名,用于无法部署到域根目录而只能部署到子目录的情况。

    createBrowserRouter(routes, {
        basename: "/app",
    });
  • future:为路由器启用的一组可选的 Future Flags

    const router = createBrowserRouter(routes, {
        future: {
            // Normalize `useNavigation()`/`useFetcher()` `formMethod` to uppercase
            v7_normalizeFormMethod: true,
        },
    });

    目前可用的 future flags 如下:

    Flag Description 说明
    v7_fetcherPersist 延迟活动的 fetcher 清理,直到它们返回到 idle 状态
    v7_normalizeFormMethod useNavigation().formMethod 规范化为大写的 HTTP 方法
    v7_partialHydration 支持服务端渲染应用程序的部分水合功能
    v7_prependBasename 将路由的基名添加到 navigate/fetch 路径的前面
    v7_relativeSplatPath 修复 splat 路由中相对路径解析的错误
  • hydrationData:在进行服务器渲染并选择退出自动水合时, hydrationData 选项允许您从服务器渲染器中传递水合数据。

    const router = createBrowserRouter(routes, {
        hydrationData: {
            loaderData: {
                // [routeId]: serverLoaderData
            },
            // may also include `errors` and/or `actionData`
        },
    });
  • window:对于浏览器 devtool 插件或测试等环境来说,使用与全局 window 不同的窗口非常有用。

createHashRouter

如果您无法配置 Web 服务器以将所有流量导向 React Router 应用程序,则此路由器非常有用。

不建议使用 Hash 路由!

功能与 createBrowserRouter 并无二致。

const router = createHashRouter([
  {
    path: "/",
    element: ,
    loader: rootLoader,
    children: [
      {
        path: "team",
        element: ,
        loader: teamLoader,
      },
    ],
  },
]);

createMemoryRouter

内存路由器不使用浏览器的历史记录,而是在内存中管理自己的历史记录堆栈。它主要用于测试和组件开发工具(如 Storybook),但也可用于在任何非浏览器环境中运行 React Router。

import * as React from "react";
import {
  RouterProvider,
  createMemoryRouter,
} from "react-router-dom";
import {
  render,
  waitFor,
  screen,
} from "@testing-library/react";
import "@testing-library/jest-dom";
import CalendarEvent from "./routes/event";

test("event route", async () => {
  const FAKE_EVENT = { name: "test event" };
  const routes = [
    {
      path: "/events/:id",
      element: ,
      loader: () => FAKE_EVENT,
    },
  ];

  const router = createMemoryRouter(routes, {
    initialEntries: ["/", "/events/123"], // 历史记录
    initialIndex: 1, // 初始化索引
  });

  render();

  await waitFor(() => screen.getByRole("heading"));
  expect(screen.getByRole("heading")).toHaveTextContent(
    FAKE_EVENT.name
  );
});

参数除了 initialIndexinitialEntries 外其它参数与 createBorwserRouter 并无二致。

  • initialEntries:历史记录堆栈中的初始条目。可以使用历史记录堆栈中已有的多个位置来启动测试(或应用)(用于测试后退导航等)

    createMemoryRouter(routes, {
        initialEntries: ["/", "/events/123"],
    });
  • initialIndex:历史堆栈中要呈现的初始索引。从特定条目开始测试。它默认为 中的最后一个条目 initialEntries

    createMemoryRouter(routes, {
      initialEntries: ["/", "/events/123"],
      initialIndex: 1, // start at "/events/123"
    });

createStaticHandler

createStaticHandler 用于在服务器端通过 呈现应用程序之前在服务器(即 Node 或其他 Javascript 运行时)上执行数据获取和提交。

import {
  createStaticHandler,
  createStaticRouter,
  StaticRouterProvider,
} from "react-router-dom/server";

// ...

const routes = [
  {
    path: "/",
    loader: exampleLoader,
    Component: Root,
    ErrorBoundary: ComponentA,
  },
];

export async function renderHtml(req) {
  let { query, dataRoutes } = createStaticHandler(routes);
  let fetchRequest = createFetchRequest(req);
  let context = await query(fetchRequest);

  // If we got a redirect response, short circuit and let our Express server
  // handle that directly
  if (context instanceof Response) {
    throw context;
  }

  let router = createStaticRouter(dataRoutes, context);
  return ReactDOMServer.renderToString(
    
      
    
  );
}

routesbasenamecreateBrowserRouter 是一样的,

  • handler.query() 方法接受 Fetch 请求,执行路由匹配,并根据请求执行所有相关的路由 action/loader 方法,返回context值包含呈现请求的 HTML 文档所需的所有信息(路由级别 actionDataloaderDataerrors 等)。如果任何匹配的路由返回或抛出重定向响应,query() 则将以 Fetch 的形式返回该重定向 Response。如果请求被中止,query 将抛出错误,例如 Error("query() call aborted: GET /path")。如果你想抛出本机 AbortSignal.reason(默认情况下为 DOMException),你可以选择加入 future.v7_throwAbortReason 未来标志。

    DOMException 是在 Node 17 中添加的,因此你必须在 Node 17 或更高版本上才能正常工作。
  • opts.requestContext 如果您需要将信息从服务器传递到 Remix action/loader,您可以使用它来执行此操作,opts.requestContext 它将显示在上下文参数中的操作/加载器中。

    const routes = [{
      path: '/',
      loader({ request, context }) {
        // Access `context.dataFormExpressMiddleware` here
      },
    }];
    
    export async function render(req: express.Request) {
      let { query, dataRoutes } = createStaticHandler(routes);
      let remixRequest = createFetchRequest(request);
      let staticHandlerContext = await query(remixRequest, {
        // Pass data from the express layer to the remix layer here
        requestContext: {
          dataFromExpressMiddleware: req.something
        }
     });
     ...
    }
  • opts.routeId

    如果你需要调用一个与 URL 不完全对应的特定路由操作/加载器(例如,父路由加载器),你可以指定routeId

    staticHandler.queryRoute(new Request("/parent/child"), {
      routeId: "parent",
    });
  • opts.requestContext

    如果您需要将信息从服务器传递到 Remix action/loader 中,您可以使用 进行传递,opts.requestContext 它将显示在上下文参数中的 action/loader 中。

createStaticRouter

createStaticRouter 是利用数据路由器在服务器(即Node或其他 Javascript 运行时)上进行渲染时,可以使用它。

import {
  createStaticHandler,
  createStaticRouter,
  StaticRouterProvider,
} from "react-router-dom/server";

// ...

const routes = [
  {
    path: "/",
    loader: exampleLoader,
    Component: ComponentA,
    ErrorBoundary: RootErrorBoundary,
  },
];

export async function renderHtml(req) {
  let { query, dataRoutes } = createStaticHandler(routes);
  let fetchRequest = createFetchRequest(req);
  let context = await query(fetchRequest);

  // If we got a redirect response, short circuit and let our Express server
  // handle that directly
  if (context instanceof Response) {
    throw context;
  }

  let router = createStaticRouter(dataRoutes, context);
  return ReactDOMServer.renderToString(
    
      
    
  );
}

RouterProvider

RouterProvider 是 react-router-dom 提供的一个组件,用于将路由配置传递给整个应用,使得应用中的所有组件都可以访问和使用这些路由信息。

主要功能:

  • 提供路由上下文:RouterProvider 创建并提供一个路由上下文,使应用中的任何组件都可以方便地访问路由信息和导航功能。
  • 管理路由状态:它负责管理路由的状态和更新,包括当前路径、导航历史等。
  • 加载数据:结合路由加载器 (loader),RouterProvider 可以在路由切换时预加载数据,提高应用的性能和用户体验。

以下是一个完整的示例,展示了如何使用 RouterProvider 配置和提供路由:

import {
  createBrowserRouter,
  RouterProvider,
} from "react-router-dom";

// 定义组件
function Home() {
  return 

Home Page

; } function About() { return

About Page

; } // 创建路由实例 const router = createBrowserRouter([ { path: "/", element: , }, { path: "/about", element: , }, ]); // 提供路由上下文 ReactDOM.createRoot(document.getElementById('root')).render( );

除了在上面的示例中用到的 router 参数外,还有 fallbaclElement,它用于在路由加载过程中显示一个备用的 UI 元素。这在需要加载数据或组件时非常有用,可以提供一个良好的用户体验,例如显示一个加载指示器或占位符,直到实际的内容加载完毕。例如:

}
/>

StaticRouterProvider

StaticRouterProvider 是 react-router-dom 提供的一个组件,主要用于在服务器端渲染(SSR)环境中进行路由配置。与客户端渲染不同,服务器端渲染需要在服务器上执行路由匹配和组件渲染,然后将渲染好的 HTML 发送到客户端。

主要用途

  • 服务器端渲染(SSR):StaticRouterProvider 适合在服务器端环境中使用,配合 React 的服务器端渲染功能,实现同构应用。
  • 静态路由配置:它使用静态路由配置,不依赖于浏览器的历史记录和导航功能。
    主要特性
  • 静态上下文:StaticRouterProvider 使用静态上下文进行路由匹配,这在服务器端环境中是必要的,因为没有浏览器的历史记录或导航功能。
  • 便于 SSR:它简化了在服务器端进行路由匹配和渲染的过程。
import {
  createStaticHandler,
  createStaticRouter,
  StaticRouterProvider,
} from "react-router-dom/server";

const routes = [
  {
    path: "/",
    loader: rootLoader,
    Component: Root,
    ErrorBoundary: RootBoundary,
  },
];

export async function renderHtml(req) {
  let { query, dataRoutes } = createStaticHandler(routes);
  let fetchRequest = createFetchRequest(req);
  let context = await query(fetchRequest);

  // If we got a redirect response, short circuit and let our Express server
  // handle that directly
  if (context instanceof Response) {
    throw context;
  }

  let router = createStaticRouter(dataRoutes, context);
  return ReactDOMServer.renderToString(
    
      
    
  );
}

这个方法的主要参数:

  • context:从createStaticHandler().query()调用返回的内容,其中包含了请求所获取的所有数据。
  • router:这是通过以下方式创建的路由器 createStaticRouter
  • hydrate:默认情况下, 将把所需的水合数据字符串化到标签 window.__staticRouterHydrationData

你可能感兴趣的:(掌握 React Router:构建你的 React 应用导航)