express - 当调用泛型处理程序(如 expressJS 中间件)时,我如何让 TypeScript 编译器对变异感到满意
免责声明:我对方差总体上仍然有些模糊……
我的情况如下:
// index.ts
import express from 'express';
import {Request, Response} from 'express';
const app = express();
app.use(handler);
interface BetterRequest extends Request {
foo: string;
}
function handler(req: BetterRequest, res: Response) {
req.foo = 'bar';
}
// tsconfig.json
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"strict": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
}
}
我得到的错误是
[ts]
Argument of type '(req: BetterRequest, res: Response) => void' is not assignable to parameter of type 'RequestHandlerParams'.
Type '(req: BetterRequest, res: Response) => void' is not assignable to type 'RequestHandler'.
Types of parameters 'req' and 'req' are incompatible.
Type 'Request' is not assignable to type 'BetterRequest'.
Property 'foo' is missing in type 'Request'.
我理解错误信息的含义,并且我可以通过 tsconfig.json 文件或一行注释来关闭这些警告。但这不是我想要的。
我应该如何正确地编写代码来处理这种情况?
以下是唯一的方法吗?
// index.ts
function handler(req: Request, res: Response) {
const req2 = req as BetterRequest;
req2.foo = 'bar';
}
答案#1
如你所建议的,显式类型转换可能是最好的方式,如果你知道会立即设置foo属性,并且希望从此刻起能够使用该属性而无需检查它是否已定义。另一种方法是将foo属性声明为可选的;这样Request可以隐式转换为BetterRequest(所以你的处理器可以隐式转换为Express处理器),但是foo属性的类型将包含undefined,并且每次使用该属性时都必须处理这个情况。
答案#2
问题出现在 app.use(handler),正如错误所示:
类型 'Request' 不能赋值给类型 'BetterRequest'。类型 'Request' 中缺少属性 'foo'。
编译器拒绝这个赋值,因为handler在说:“我期望调用者给我一个‘BetterRequest’”,但app.use()却在说:“对于你给我的任何处理器,我只能保证传入一个‘Request’。”
那么,我认为最优雅的解决方案是:
app.use(handler as RequestHander)
这种类型断言告诉 app.use(),handler 是一个 RequestHandler,而实际上它是一个 “BetterRequestHandler”。只要 handler 总是安全/正确地引用 req.foo,这是可以的。
我认为这比 Matt McCutchen 提出的将 req.foo 设为可选的建议更好,因为即使你知道这个值存在,也不必为了让编译器满意而不断检查它的值。
作为一个具体的例子,我的用例是在主处理程序之前使用一个验证中间件:
interface ValidatedRequest<T extends object> extends Request {
validation: T,
}
function registerValidate(
req: ValidatedRequest<RegisterInput>,
res: Response,
next: NextFunction,
) {
const validation : string | RegisterInput = validate<RegisterInput>(req.body);
if (typeof validation === 'string') {
return next(badRequest(validation));
} else {
req.validation = validation;
next();
}
}
function register(
req: ValidatedRequest<RegisterInput>,
res: Response,
next: NextFunction
) {
// Use req.validation
}
app.post(
'/path',
registerValidate as RequestHandler,
register as RequestHandler
);
