Back

React Router 中间件入门指南

React Router 中间件入门指南

引言

如果你曾经需要在每个受保护的路由之前检查身份验证、在整个应用程序中记录请求日志,或者在 React Router 的父子路由之间共享数据,你很可能多次编写过相同的代码。React Router 中间件通过让你在请求到达 loader 和 action 之前拦截并处理请求,解决了这种重复性问题。

本指南涵盖了在 7.9+ 版本中实现 React Router 中间件所需的一切内容,从启用 v8_middleware 标志到使用新的中间件 API 创建你的第一个身份验证中间件。

核心要点

  • React Router 7 引入了中间件来处理跨切面关注点,如身份验证和日志记录
  • 中间件在嵌套链中按顺序运行,实现路由之间的高效数据共享
  • 新的 context API 提供类型安全的数据共享,避免命名冲突
  • 支持服务端和客户端中间件,具有不同的执行行为

什么是 React Router 中间件?

React Router 7 中间件是一个在路由处理程序执行前后运行代码的特性。可以将其视为一个处理管道,每个中间件都可以检查请求、向上下文添加数据,甚至通过重定向来短路请求。

与 React Router 6 中 loader 的并行执行不同,中间件在嵌套链中按顺序运行。父中间件在向下执行时先于子中间件执行,然后在生成响应后以相反的顺序向上执行。

父中间件 → 子中间件 → 路由处理程序 → 子中间件 → 父中间件

这种顺序执行使得以前不可能实现的模式成为可能,例如从父路由向所有子路由共享已认证的用户数据,而无需冗余的数据库调用。

在项目中启用中间件

要开始使用中间件,首先确保你运行的是 React Router 7.9.0 或更高版本(或启用了中间件标志的 7.3.0+)。然后在配置中启用特性标志:

// react-router.config.ts
import type { Config } from "@react-router/dev/config";

export default {
  future: {
    v8_middleware: true,
  },
} satisfies Config;

此标志启用稳定的中间件支持和新的 context API。loader 和 action 中的 context 参数现在可以通过类型安全的 API 访问中间件设置的数据。

理解 Context API

React Router 7 中新的 context API 提供了一个类型安全的类 Map 接口,用于在中间件和路由处理程序之间共享数据。现在不再直接将属性附加到 context 对象上,而是使用 context.set()context.get() 配合类型化的 context 键:

import { createContext } from "react-router";

// 创建一个类型化的 context 键
const userContext = createContext<User>();

// 在中间件中
context.set(userContext, user);

// 在 loader/action 中
const user = context.get(userContext); // 类型安全的 User 对象

这种方法消除了 TypeScript 类型断言,并防止不同中间件之间的命名冲突。

创建你的第一个中间件

让我们构建一个保护路由并共享用户数据的身份验证中间件:

// app/middleware/auth.ts
import { redirect, createContext } from "react-router";
import type { Route } from "./+types/root";

export const userContext = createContext<User | null>();

export const authMiddleware: Route.Middleware = async ({ 
  request, 
  context,
  next
}) => {
  const user = await getUserFromSession(request);
  
  if (!user) {
    throw redirect("/login");
  }
  
  context.set(userContext, user);
  return next();
};

将此中间件应用于受保护的路由:

// app/routes/dashboard.tsx
import { authMiddleware, userContext } from "~/middleware/auth";

export  const  middleware: Route.MiddlewareFunction[] = [authMiddleware];

export async function loader({ context }: Route.LoaderArgs) {
  const user = context.get(userContext); // 保证存在
  const profile = await getProfile(user.id);
  return { profile };
}

中间件在 loader 之前运行,确保经过身份验证的访问,并提供用户对象而无需额外的数据库查询。

服务端中间件 vs 客户端中间件

React Router 支持服务端和客户端中间件,具有略微不同的行为:

服务端中间件在服务器上运行,必须返回一个 Response:

export const middleware: Route.MiddlewareFunction[] = [
  async ({ request }, next) => {
    console.log(`Server: ${request.method} ${request.url}`);
    const response = await next();
    response.headers.set("X-Custom-Header", "value");
    return response; // 必需
  };
];

客户端中间件在客户端导航期间在浏览器中运行:

export const middleware: Route.ClientMiddlewareFunction[] = [
  async ({ next }) => {
    const start = performance.now();
    await next();
    console.log(`Navigation took ${performance.now() - start}ms`);
  },
];

常见中间件模式

日志中间件

export const loggingMiddleware: Route.Middleware = async ({ request, next }) => {
  const requestId = crypto.randomUUID();
  console.log(`[${requestId}] ${request.method} ${request.url}`);
  
  const response = await next();
  
  console.log(`[${requestId}] Response ${response.status}`);
  return response;
};

组合多个中间件

你可以将多个中间件函数链接在一起:

// app/routes/admin.tsx
export const middleware = [
  loggingMiddleware,
  authMiddleware,
  adminRoleMiddleware
];

每个中间件按顺序执行,任何中间件都可以通过抛出重定向或错误响应来短路链。

结论

React Router 中间件改变了你在应用程序中处理跨切面关注点的方式。通过启用 v8_middleware 标志并采用新的 context API,你可以消除代码重复,在路由之间高效共享数据,并以清晰、可重用的方式实现身份验证、日志记录和其他模式。

从简单的中间件(如身份验证或日志记录)开始,然后随着应用程序的增长探索高级模式。顺序执行模型和类型安全的 context API 使中间件成为你 React Router 工具包中的强大补充。

常见问题

不可以,中间件是 React Router 7.3 中通过标志引入的新特性,在 7.9 版本中通过 `future.v8_middleware` 稳定化。早期版本需要通过包装组件或自定义 loader 逻辑来实现类似功能。

中间件可以通过减少冗余操作来提高性能。由于中间件按顺序运行并可以通过 context 共享数据,你可以避免并行 loader 可能出现的重复数据库查询或 API 调用。

当中间件抛出错误时,React Router 会像处理任何其他路由错误一样处理它。错误会冒泡到最近的错误边界,链中的后续中间件将不会执行。

中间件不能直接修改 request 对象,但它可以向 context 添加 loader 和 action 可以访问的数据。要修改请求,需要创建一个新的 Request 对象并将其传递给下一个中间件。

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay