Express与jsonwebtoken实现JWT签名时的运行时类型不兼容问题排查与修复
我来帮你分析并解决这个问题,先理清楚你遇到的核心矛盾:代码在VSCode里没有类型报错,但运行时却抛出了类型不兼容的错误,本质是自定义请求类型和Express默认类型的匹配问题。
先贴一下你的代码方便参考:
import jwt from 'jsonwebtoken'; import { Request, Response } from 'express'; import { JWT_EXPIRY, JWT_SECRET } from '../../config'; interface UserParams { username: string, password: string } interface ReqWithUserParams extends Request { user: UserParams } const createAuthToken = (user: UserParams): string => { return jwt.sign({ user }, JWT_SECRET, { subject: user?.username, expiresIn: JWT_EXPIRY, algorithm: 'HS256' }); }; export const login = (req: ReqWithUserParams, res: Response) => { const authToken = req.user && createAuthToken(req.user); if (authToken) { res.json({ authToken }); } else { res.status(401).json({ message: 'Unauthorized' }); } }; export const refresh = (req: ReqWithUserParams, res: Response) => { const authToken = req.user && createAuthToken(req.user); if (authToken) { res.json({ authToken }); } else { res.status(401).json({ message: 'Unauthorized' }); } };
你遇到的运行时错误信息如下:
The last overload gave the following error.
Argument of type '(req: ReqWithUserParams, res: Response) => void' is not assignable to parameter of type 'RequestHandlerParams<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'.
Type '(req: ReqWithUserParams, res: Response) => void' is not assignable to type 'RequestHandler<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'.
Types of parameters 'req' and 'req' are incompatible.
Type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>' is not assignable to type 'ReqWithUserParams'.
Types of property 'user' are incompatible.
Type 'User | undefined' is not assignable to type 'UserParams'.
Type 'undefined' is not assignable to type 'UserParams'.
为什么会出现这个问题?
VSCode里没报错是因为你直接给req指定了ReqWithUserParams类型,相当于在静态类型检查阶段告诉TS:这个req一定有符合UserParams的user属性。但实际运行时,Express传入的原生Request对象中,user属性本身是可能为undefined的(或者是Express默认的User类型,和你定义的UserParams不匹配)。
当你把login/refresh作为Express路由处理函数时,Express会传入它自己的Request对象,这个对象的user属性并不满足你自定义的UserParams类型要求,所以运行时就触发了类型不兼容的错误。简单说就是:你在TS里强行给req指定了更严格的类型,但实际传入的req并不符合,静态检查被“骗过”了,但运行时露馅了。
怎么修复?
有两种合理的修复方式,你可以根据项目场景选择:
方式一:调整自定义接口,兼容undefined
修改你的ReqWithUserParams接口,让user属性变成可选(允许为undefined),这样就能和Express默认的Request类型兼容:
// 修改后的接口定义 interface ReqWithUserParams extends Request { user?: UserParams // 加问号标记为可选属性 }
你已经在处理函数里写了req.user的存在性判断,所以这个修改不会影响业务逻辑,同时能解决类型不兼容的问题。
方式二:全局扩展Express的Request类型(更推荐)
如果希望整个项目都能使用自定义的User类型,可以直接扩展Express的默认类型,不用每次都写自定义接口。
在项目中创建types/express/index.d.ts文件(路径可自行调整,确保TS能识别到),写入以下内容:
declare namespace Express { export interface Request { user?: import('../../path-to-your-user-params').UserParams; // 替换为UserParams的实际文件路径 } }
之后在路由处理函数里直接使用原生Request类型即可,它会自动包含user?: UserParams的属性:
export const login = (req: Request, res: Response) => { const authToken = req.user && createAuthToken(req.user); if (authToken) { res.json({ authToken }); } else { res.status(401).json({ message: 'Unauthorized' }); } };
另外提个小细节:createAuthToken函数的参数user是UserParams类型,其中username是必填项,所以user?.username里的问号可以去掉,避免不必要的可选链判断。
备注:内容来源于stack exchange,提问作者ossiggy

