我试图添加一个属性来表达使用typescript从中间件请求对象。但是,我不知道如何向对象添加额外的属性。如果可能的话,我宁愿不用括号。
我正在寻找一个解决方案,允许我写类似的东西(如果可能的话):
app.use((req, res, next) => {
req.property = setProperty();
next();
});
我试图添加一个属性来表达使用typescript从中间件请求对象。但是,我不知道如何向对象添加额外的属性。如果可能的话,我宁愿不用括号。
我正在寻找一个解决方案,允许我写类似的东西(如果可能的话):
app.use((req, res, next) => {
req.property = setProperty();
next();
});
当前回答
所有这些回答在某种程度上似乎都是错误的或过时的。
这在2020年5月对我有效:
在$ {PROJECT_ROOT} / @types /快递/ index.d.ts:
import * as express from "express"
declare global {
namespace Express {
interface Request {
my_custom_property: TheCustomType
}
}
}
在tsconfig。Json,添加/合并属性,使:
"typeRoots": [ "@types" ]
欢呼。
其他回答
在TypeScript中,接口是开放的。这意味着只需重新定义它们,就可以从任何地方向它们添加属性。
考虑到您正在使用这个express.d.ts文件,您应该能够重新定义Request接口以添加额外的字段。
interface Request {
property: string;
}
然后在中间件函数中,req参数也应该具有此属性。您应该能够在不修改代码的情况下使用它。
这招对我很管用:
declare namespace e {
export interface Request extends express.Request {
user:IUserReference,
[name:string]:any;
}
export interface Response extends express.Response {
[name:string]:any;
}
}
export type AsyncRequestHandler = (req:e.Request, res:e.Response, logger?:Logger) => Promise<any>|Promise<void>|void;
export type AsyncHandlerWrapper = (req:e.Request, res:e.Response) => Promise<void>;
我在代码中使用了它,比如以这样的方式导出具有这样签名的函数:
app.post('some/api/route', asyncHandlers(async (req, res) => {
return await serviceObject.someMethod(req.user, {
param1: req.body.param1,
paramN: req.body.paramN,
///....
});
}));
我已经改变了响应类型,包括ApiResponse(一个自定义响应对象)response <ApiResponse>
export interface ApiResponse {
status?: string
error?: string
errorMsg?: string
errorSubject?: string
response?: any
}
const User = async (req: Request, res: Response<ApiResponse>, next: NextFunction) => {
try {
if (!username) return res.status(400).send({ errorMsg: 'Username is empty' })
/* ... */
} catch(err){
/* ... */
}
}
可选择的解决方案
这实际上并没有直接回答这个问题,但我提供了一个替代方案。我也在与同样的问题作斗争,并尝试了该页面上几乎所有的界面扩展解决方案,但没有一个有效。
这让我停下来思考:“为什么我实际上在修改请求对象?”
使用response.locals
Express开发人员似乎认为用户可能希望添加自己的属性。这就是为什么有一个locals对象。问题是,它不在请求中,而是在响应对象中。
响应。Locals对象可以包含您可能希望拥有的任何自定义属性,这些属性封装在请求-响应周期中,因此不会暴露给来自不同用户的其他请求。
需要存储userId?只需设置response.locals.userId = '123'。没有必要纠结于打字。
它的缺点是,您必须传递响应对象,但很可能您已经这样做了。
https://expressjs.com/en/api.html#res.locals
打字
另一个缺点是缺乏类型安全性。然而,你可以在Response对象上使用泛型类型来定义body和locals对象的结构:
响应< MyResponseBody, MyResponseLocals >
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/express/index.d.ts#L127
警告
你不能保证userId属性确实在那里。您可能希望在访问它之前进行检查,特别是在对象的情况下。
使用上面的例子来添加一个userId,我们可以有这样的东西:
interface MyResponseLocals {
userId: string;
}
const userMiddleware = (
request: Request,
response: Response<MyResponseBody, MyResponseLocals>,
next: NextFunction
) => {
const userId: string = getUserId(request.cookies.myAuthTokenCookie);
// Will nag if you try to assign something else than a string here
response.locals.userId = userId;
next();
};
router.get(
'/path/to/somewhere',
userMiddleware,
(request: Request, response: Response<MyResponseBody, MyResponseLocals>) => {
// userId will have string type instead of any
const { userId } = response.locals;
// You might want to check that it's actually there
if (!userId) {
throw Error('No userId!');
}
// Do more stuff
}
);
虽然这是一个非常古老的问题,但我最近偶然发现了这个问题。接受的答案工作得很好,但我需要添加一个自定义接口请求-一个接口,我一直在我的代码中使用,并不能很好地与接受的答案。从逻辑上讲,我尝试了这样做:
import ITenant from "../interfaces/ITenant";
declare namespace Express {
export interface Request {
tenant?: ITenant;
}
}
但这不起作用,因为Typescript处理。d。Ts文件作为全局导入,当它们中有导入时,它们被视为普通模块。这就是为什么上面的代码不能在标准的typescript设置下工作。
这是我最后做的事情
// typings/common.d.ts
declare namespace Express {
export interface Request {
tenant?: import("../interfaces/ITenant").default;
}
}
// interfaces/ITenant.ts
export interface ITenant {
...
}