如何在 Typescript 中使用缩减合并来使用新的通用参数正确扩展通用接口?

问题描述 投票:0回答:1

我有一个基本的 Express 应用程序。提供基于令牌的身份验证。处理 zod 以创建模式来验证传入数据。

说我有两个模式:

  • createuserSchema {名字、姓氏、电子邮件、通行证、passConfirm}
  • loginUserSchema {电子邮件,密码}

Zod 允许我们根据我们的模式推断类型,例如:

type SchemaBasedType = z.infer<typeof schmea>
根据我的模式,我有两种类型:CreateUserRequestLoginUserRequest

首先像这样创建验证中间件:

export const validateRequest =
    <T extends ZodTypeAny>(schema: T): RequestHandler =>
    async (req, res, next) => {
        try {
            const userRequestData: Record<string, unknown> = req.body;

            const validationResult = (await schema.spa(userRequestData)) as z.infer<T>;

            if (!validationResult.success) {
                throw new BadRequest(fromZodError(validationResult.error).toString());
            }

            req.payload = validationResult.data;
            next();
        } catch (error: unknown) {
            next(error);
        }
    }; 

如上所述,该中间件接受根据 zod 文档键入的模式参数。 在我看来,使用“payload”属性扩展请求对象是一个很好的决定,我可以在其中放置有效数据。

然后问题就开始了。 TS 不知道有效负载实际上是什么。这就是声明合并即将到来的地方。 首先,尝试这样的事情很诱人:

declare global {
    namespace Express {
        export interface Request {
            payload?: any;
            
        }
    }
}

但这似乎不是一个好主意,因为我们确切地知道我们的有效负载签名是什么。 然后我尝试了基于 zod 类型的联合类型:

payload?: CreateUserRequest | LoginUserRequest;

通过这种方法,我发现了一个错误,一些更窄类型的字段在其他类型中不存在;

然后我尝试使用通用,

declare global {
    namespace Express {
        export interface Request<T> {
            payload?: T;
            
        }
    }
}

这似乎是一个解决方案,但是 Request 接口已经有 5 个通用参数:

interface Request<
    P = ParamsDictionary,
    ResBody = any,
    ReqBody = any,
    ReqQuery = ParsedQs,
    LocalsObj extends Record<string, any> = Record<string, any>

我什至无法想象它应该如何合并,这意味着我的通用参数将是第一个,还是最后一个? 无论如何,这似乎是一种错误的方式,因为毕竟我没有通过 IDE 提示看到扩展接口。 在堆栈的某个地方我遇到了这种方法:

declare global {
    namespace Express {
        export interface Request<
            Payload = any,
            P = ParamsDictionary,
            ResBody = any,
            ReqBody = any,
            ReqQuery = ParsedQs,
            LocalsObj extends Record<string, any> = Record<string, any>
        > {
            payload?: Payload;
        }
    }
}

它甚至在鼠标引导后给了我一个提示,但我不确定 any 是一个好的类型,因为我们已经有了由 zod 推断的类型。如果未指定 Payload = any,我不会收到类型提示。

我没有想法和堆栈,因为我不是 ts 和后端架构方面的专家;

最后,我想要得到这样的东西:

authRouter.post("/register", validateRequest(createUserSchema), AuthController.register);
编译器知道有效负载签名等于 CreateUserRequest
authRouter.post("/login", validateRequest(loginUserSchema), AuthController.login)
; 编译器知道有效负载签名等于 LoginUserRequest

我应该在哪里正确指定我的预期类型以及如何处理它们?

typescript express merge request type-declaration
1个回答
0
投票

花了时间寻找解决方案,得出以下结论:

  • 正如@jcalz提到的,我们不能让通用接口变得更通用;

  • 我所有尝试根据传递的模式明确输入有效负载属性,而不使用通用参数,例如:

    type Parse =(模式:T,数据:未知)=> z.infer
    导出类型 ParsedObj = ReturnType

有效负载是:

declare global {
    namespace Express {
        export interface Request {
            payload?: ParsedObj;
        }
    }
}

没有成功,因为它依赖于一个通用参数,即调用之前的“any”,所以接下来的代码中我收到“any”。

因此,只能信任 zod 验证的结果,因为它只选择模式中描述的 props,我们不会收到未使用的属性。

如果您根据您使用快递的经验,有更好的方法,欢迎您。

© www.soinside.com 2019 - 2024. All rights reserved.