英文:
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<B extends any, P extends any, Q extends any>
{
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<{ email: string; password: string; }>, undefined, undefined> {}
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) =>
{
...
}
const response = await login({ body: { email: 'mail@test.com', password: 'verystrongpassword' }, 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<B extends any, P extends any, Q extends any>
{
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<T>
,它生成一个版本的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<T>
和键重映射类型的组合,它将所有属性变为可选属性,并且抑制所有接受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;
};
} */
这里body
和params
是可选的,因为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<T>
that produces a version of T
such that any property accepting undefined
is optional. It could look like this:
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
That's a combination of Partial<T>
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<B, P, Q> = UndefinedIsOptional<{
body: B;
params: P;
query: Q;
}>
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<{ email: string; password: string; }, undefined, undefined>
/* 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<
{ email: string; password: string; }, undefined, undefined> {
}
Also, let's just look at what happens when the property includes undefined
but is not only undefined
:
type Other = IAPIRequest<{ a: string } | undefined, number | undefined, { b: number }>;
/* 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.
答案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<B , P = undefined, Q = undefined> {
body: B;
params?: P;
query?: Q;
}
Then you don't have to provide the default when using it:
export interface ILoginRequest extends IAPIRequest<{ email: string; password: string; }> {}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论