Typescript类型扩展

huangapple go评论71阅读模式
英文:

Typescript type extension

问题

我试图定义一个自定义接口,像这样:

export interface IAPIRequest<B extends any, P extends any, Q extends any> {
  body: B;
  params: P;
  query: Q;
}

这个类型应该在许多其他类型中进行扩展,用于处理我的API的每个请求。

例如:

export interface ILoginRequest extends IAPIRequest<{ email: string; password: string; }, undefined, undefined> {}

它有点工作,但每次我使用这个接口时,即使它们是undefined,我都必须提供所有属性。

示例:

const login = async ({ body }: ILoginRequest) => {
  // ...
}

const response = await login({ body: { email: 'mail@test.com', password: 'verystrongpassword' }, params: undefined, query: undefined });

如果我不提供undefined属性,它就无法工作。

如何定义一个抽象类型,使IAPIRequest不需要我提供undefined值?

附注:我也尝试过这样做

export interface IAPIRequest<B extends any, P extends any, Q extends any> {
  body?: B;
  params?: P;
  query?: Q;
}

即使对于IAPIRequest<B, P, Q>,其中B、P或Q都不允许为undefined,我仍然得到属性可能为undefined的提示。

英文:

I try to define a custom interfaces like this :

export interface IAPIRequest&lt;B extends any, P extends any, Q extends any&gt;
{
  body: B;
  params: P;
  query: Q;
}

This type is supposed to be extended in a lot of other types for each request mu API is supposed to handle.

For example :

export interface ILoginRequest extends IAPIRequest&lt;{ email: string; password: string; }&gt;, undefined, undefined&gt; {}

It works a little but everytime I use this interface, I must provide all the properties even if they are undefined.

Example:

const login = async ({ body }: ILoginRequest) =&gt; 
{
  ...
}

const response = await login({ body: { email: &#39;mail@test.com&#39;, password: &#39;verystrongpassword&#39; }, params: undefined, query: undefined });

It doesn't work if I don't provide the undefined properties.

How can I define an abstract type for IAPIRequest that would avoid me from providing undefined values ?

PS : I've tried this as well

export interface IAPIRequest&lt;B extends any, P extends any, Q extends any&gt;
{
  body?: B;
  params?: P;
  query?: Q;
}

Even for IAPIRequest<B, P, Q> where none of B, P, or Q allow undefined, I still get that the properties might be undefined

答案1

得分: 1

TypeScript不会自动将接受undefined的属性视为可选的(尽管相反的情况,将可选属性视为接受undefined真的,除非启用了--exactOptionalPropertyTypes)。 这在microsoft/TypeScript#12400上有一个长期的开放功能请求(标题是关于可选函数参数,而不是对象属性,但似乎问题已扩展到包括对象属性)。 尽管讨论中描述了各种解决方法,但没有实施。

让我们定义自己的解决方法; 一个实用类型UndefinedIsOptional&lt;T&gt;,它生成一个版本的T,以便接受undefined的任何属性都是可选的。 它可能如下所示:

type UndefinedIsOptional<T extends object> = (Partial<T> &
    { [K in keyof T as undefined extends T[K] ? never : K]: T[K] }
) extends infer U ? { [K in keyof U]: U[K] } : never

这是Partial&lt;T&gt;键重映射类型的组合,它将所有属性变为可选属性,并且抑制所有接受undefined的属性。 这两者的交集实际上是您想要的(可选属性和必需属性的交集是必需属性),但我使用了一个在 https://stackoverflow.com/q/57683303/2887218 中描述的技巧,以更易于阅读的方式显示类型。

然后我们可以定义您的类型如下:

type IAPIRequest<B, P, Q> = UndefinedIsOptional<{
    body: B;
    params: P;
    query: Q;
}>;

请注意,这必须是一个type别名,而不是一个interface,因为编译器需要确切地知道哪些属性将出现(以及它们的可选性)。 这在您的示例代码中可能不重要,但您应该了解这一点。

让我们进行测试:

type ILR = IAPIRequest<{ email: string; password: string; }, undefined, undefined>;
/* type ILR = {
    body: {
        email: string;
        password: string;
    };
    params?: undefined;
    query?: undefined;
} */

这看起来像您想要的,因此您可以定义您的ILoginRequest接口:

interface ILoginRequest extends IAPIRequest<{ email: string; password: string; }, undefined, undefined> {
}

另外,让我们看看当属性包括undefined但不仅限于undefined时会发生什么:

type Other = IAPIRequest<{ a: string } | undefined, number | undefined, { b: number }>;
/* type Other = {
    body?: {
        a: string;
    } | undefined;
    params?: number | undefined;
    query: {
        b: number;
    };
} */

这里bodyparams是可选的,因为undefined是可能的,但query不是,因为undefined是不可能的。

英文:

TypeScript doesn't automatically treat properties that accept undefined to be optional (although the converse, treating optional properties as accepting undefined, is true, unless you've enabled --exactOptionalPropertyTypes). There is a longstanding open feature request for this at microsoft/TypeScript#12400 (the title is about optional function parameters, not object properties, but the issue seems to have expanded to include object properties also). Nothing has been implemented there, although the discussion describes various workarounds.

Let's define our own workaround; a utility type UndefinedIsOptional&lt;T&gt; that produces a version of T such that any property accepting undefined is optional. It could look like this:

type UndefinedIsOptional&lt;T extends object&gt; = (Partial&lt;T&gt; &amp;
    { [K in keyof T as undefined extends T[K] ? never : K]: T[K] }
) extends infer U ? { [K in keyof U]: U[K] } : never

That's a combination of Partial&lt;T&gt; which turns all properties optional, and a key remapped type that suppresses all undefined-accepting properties. The intersection of those is essentially what you want (an intersection of an optional prop and a required prop is a required prop) but I use a technique described at https://stackoverflow.com/q/57683303/2887218 to display the type in a more palatable manner.

Then we can define your type as

type IAPIRequest&lt;B, P, Q&gt; = UndefinedIsOptional&lt;{
    body: B;
    params: P;
    query: Q;
}&gt;

and note that this must be a type alias and not an interface because the compiler needs to know exactly which properties will appear (and apparently their optional-ness) to be an interface. This won't matter much with your example code but you should be aware of it.

Let's test it out:

type ILR = IAPIRequest&lt;{ email: string; password: string; }, undefined, undefined&gt;
/* type ILR = {
    body: {
        email: string;
        password: string;
    };
    params?: undefined;
    query?: undefined;
} */

That looks like what you wanted, so you can define your ILoginRequest interface:

interface ILoginRequest extends IAPIRequest&lt;
    { email: string; password: string; }, undefined, undefined&gt; {
}

Also, let's just look at what happens when the property includes undefined but is not only undefined:

type Other = IAPIRequest&lt;{ a: string } | undefined, number | undefined, { b: number }&gt;;
/* type Other = {
    body?: {
        a: string;
    } | undefined;
    params?: number | undefined;
    query: {
        b: number;
    };
} */

Here body and params are optional because undefined is possible, but query is not because undefined is impossible.

Playground link to code

答案2

得分: -1

不需要为这种情况扩展你的通用类型,你可以设置默认值。

export interface IAPIRequest<B, P = undefined, Q = undefined> {
    body: B;
    params?: P;
    query?: Q;
}

然后在使用时无需提供默认值:

export interface ILoginRequest extends IAPIRequest<{ email: string; password: string; }> {}
英文:

You don't need to extend your generic types for that case, you can set default values instead.

export interface IAPIRequest&lt;B , P = undefined, Q = undefined&gt; {
    body: B;
    params?: P;
    query?: Q;
}

Then you don't have to provide the default when using it:

export interface ILoginRequest extends IAPIRequest&lt;{ email: string; password: string; }&gt; {}

huangapple
  • 本文由 发表于 2023年6月2日 00:26:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/76383945.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定