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

TypeScript中如何为Express.Request添加多类型自定义属性?

解决Express Request自定义属性多类型冲突问题

你碰到的问题本质是重复全局扩展Request接口导致的类型合并冲突——TypeScript会把多个文件里对同一个接口的属性定义合并,所以store的类型会变成两个函数类型的交集(而交集在这里是无效的,因为参数和返回值都不兼容),从而报错。

下面提供两种不需要条件类型的解决方案:


方案一:全局联合类型+配套Request子类型

步骤1:全局定义联合类型的store

在项目的类型声明文件(比如types/express.d.ts)里统一扩展Request,把store定义为可选的联合类型:

// types/express.d.ts
declare module 'express-serve-static-core' {
  interface Request {
    // 定义store为两种可能的函数类型的联合
    store?: ((name: string) => number) | ((key: number) => string);
  }
}

// 确保这个文件被TypeScript识别
export {};

步骤2:在中间件文件里明确赋值并导出配套Request类型

在a.ts里,给store赋值对应类型的函数,同时导出一个标记了当前store类型的Request子类型:

// a.ts
import { Request, Response, NextFunction } from 'express';

// 假设setInStore是你已实现的函数
const setInStore = (name: string): number => {
  return parseInt(name, 10) || 0;
};

const func1 = (req: Request, res: Response, next: NextFunction) => {
  // 直接赋值对应类型的函数,TypeScript会自动校验
  req.store = (name: string): number => {
    return setInStore(name);
  };
  next();
};

// 导出配套的Request类型,供使用者明确类型
export type RequestWithSetStore = Request & {
  store: (name: string) => number;
};

export default func1;

b.ts做类似处理:

// b.ts
import { Request, Response, NextFunction } from 'express';

// 假设getFromStore是你已实现的函数
const getFromStore = (key: number): string => {
  return `value_${key}`;
};

const func2 = (req: Request, res: Response, next: NextFunction) => {
  req.store = (key: number): string => {
    return getFromStore(key);
  };
  next();
};

// 导出配套的Request类型
export type RequestWithGetStore = Request & {
  store: (key: number) => string;
};

export default func2;

步骤3:在使用时导入对应Request类型

在c.ts里,使用中间件后,在路由处理函数里明确导入对应的Request子类型,就能获得正确的类型提示:

// c.ts
import express from 'express';
import func1, { RequestWithSetStore } from './a';
import func2, { RequestWithGetStore } from './b';

const app = express();

// 使用func1的路由组
app.use(func1);
app.get('/set', (req: RequestWithSetStore, res) => {
  // 这里store的类型是明确的 (name: string) => number
  const result = req.store('123');
  res.send(`Set result: ${result}`);
});

// 使用func2的独立路由组
const getRouter = express.Router();
getRouter.use(func2);
getRouter.get('/get', (req: RequestWithGetStore, res) => {
  // 这里store的类型是明确的 (key: number) => string
  const result = req.store(123);
  res.send(`Get result: ${result}`);
});

app.use(getRouter);

app.listen(3000, () => console.log('Server running on port 3000'));

方案二:泛型中间件约束

通过泛型定义中间件的类型,让每个中间件关联特定的store类型,避免全局类型冲突:

步骤1:定义泛型中间件类型

在类型文件里定义一个泛型的中间件类型,关联自定义的store类型:

// types/middleware.ts
import { RequestHandler, Request } from 'express';

// 泛型中间件类型,指定Request必须包含特定类型的store
export type StoreMiddleware<T> = RequestHandler<
  {},
  any,
  any,
  any,
  Request & { store: T }
>;

步骤2:编写泛型中间件

a.ts里使用泛型中间件类型:

// a.ts
import { Response, NextFunction } from 'express';
import { StoreMiddleware } from './types/middleware';

const setInStore = (name: string): number => {
  return parseInt(name, 10) || 0;
};

// 明确指定泛型参数为当前store的类型
const func1: StoreMiddleware<(name: string) => number> = (req, res, next) => {
  req.store = (name) => setInStore(name);
  next();
};

export default func1;

b.ts同理:

// b.ts
import { Response, NextFunction } from 'express';
import { StoreMiddleware } from './types/middleware';

const getFromStore = (key: number): string => {
  return `value_${key}`;
};

const func2: StoreMiddleware<(key: number) => string> = (req, res, next) => {
  req.store = (key) => getFromStore(key);
  next();
};

export default func2;

步骤3:使用时推断或明确类型

在c.ts里,使用中间件后,路由处理函数的req会自动推断出store的类型(部分场景可能需要手动指定):

// c.ts
import express from 'express';
import func1 from './a';
import func2 from './b';

const app = express();

app.use(func1);
// 这里req的store类型会自动推断为(name: string) => number
app.get('/set', (req, res) => {
  const result = req.store('456');
  res.send(`Set result: ${result}`);
});

const getRouter = express.Router();
getRouter.use(func2);
// 这里req的store类型会自动推断为(key: number) => string
getRouter.get('/get', (req, res) => {
  const result = req.store(456);
  res.send(`Get result: ${result}`);
});

app.use(getRouter);

app.listen(3000, () => console.log('Server running on port 3000'));

两种方案都能实现你的需求:使用不同中间件时,store拥有对应的类型,且不会出现全局类型冲突。

内容的提问来源于stack exchange,提问作者Saman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 21:10:38