在调用通用处理程序(例如expressJS中间件)时,如何使TypeScript编译器对差异感到满意

时间:2018-10-24 21:51:47

标签: typescript express covariance contravariance

免责声明:我对总体差异还是有点模糊...

我的情况如下:

// 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'.

enter image description here

我了解错误所在,我可以关闭tsconfig.json文件中的警告,也可以通过行注释将其关闭。 但这不是我所追求的。

如何正确编写代码来处理这种情况?

以下是唯一的方法吗?

// index.ts
function handler(req: Request, res: Response) {
    const req2 = req as BetterRequest;
    req2.foo = 'bar';
}

2 个答案:

答案 0 :(得分:2)

如果您知道要立即设置foo属性,并且希望从那时开始使用该属性,而无需检查其是否已定义,则建议的显式强制转换可能是最好的方法。另一种方法是将foo属性声明为可选属性;那么Request可以隐式转换为BetterRequest(这样您的处理程序可以隐式转换为Express处理程序),但是foo属性的类型将包括undefined并且您每次使用该属性时都必须处理该问题。

答案 1 :(得分:0)

该问题发生在app.use(handler),因为错误指出:

类型“ Request”不能分配给类型“ BetterRequest”。类型“请求”中缺少属性“ foo”。

编译器拒绝此分配,因为handler在说:“我希望调用者给我一个'BetterRequest',但是app.use()在说:“对于您给我的任何处理程序,我只能保证我会传递“请求”。

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

app.use(handler as RequestHander)

此类型断言告诉app.use() handlerRequestHandler,而实际上是“ 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
);