在调用通用处理程序(如 expressJS 中间件)时,如何让 TypeScript 编译器对变化感到满意

how do I make the TypeScript compiler happy about variance when calling generic handlers (like expressJS middleware)

免责声明:总的来说,我对方差还是有点模糊...

我的情况如下:

// 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';
}

如果您知道要立即设置 foo 属性 并且希望能够使用 属性,那么按照您的建议进行显式转换可能是最好的方法从那时起,无需检查它是否已定义。另一种方法是将 foo 属性 声明为可选的;那么 Request 可以隐式转换为 BetterRequest (因此您的处理程序可以隐式转换为 Express 处理程序),但是 foo 属性 的类型将包括 undefined 并且每次使用 属性.

时你都必须处理这个问题

问题发生在 app.use(handler),因为,如错误所述:

Type 'Request' is not assignable to type 'BetterRequest'. Property 'foo' is missing in type 'Request'.

编译器拒绝这个赋值,因为 handler 是说,"I'm expecting that the caller will give me a 'BetterRequest', but app.use() is saying, "对于你给我的任何处理程序,我只能保证我会传递一个 'Request'"。

我认为最优雅的解决方案是:

app.use(handler as RequestHander)

此类型断言告诉 app.use() handler 是一个 RequestHandler,而实际上它是一个“BetterRequestHandler”。这很好,只要 handler 总是 safely/correctly 引用 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
);