[英]Optional Generic Based On Function Argument
I want to validate my XHR requests via JSON schema.我想通过 JSON 模式验证我的 XHR 请求。 I have validation functions for each response type.我有每种响应类型的验证功能。 If a validation function is specified, the response type from my XHR handler should be extracted from the validation function.如果指定了验证函数,则应从验证函数中提取来自我的 XHR 处理程序的响应类型。 If a validation function is not specified, I want the response type to be mixed
so that the unknown response data must be dealt with.如果未指定验证函数,我希望mixed
响应类型,以便必须处理未知的响应数据。
So I have this:所以我有这个:
type HTTPMethod =
| 'GET'
| 'POST'
;
type ResponseValidator<Response> = (mixed) => Response;
type HTTPRequest<
Method: HTTPMethod,
Response: mixed,
> = {
url: string,
method: Method,
responseValidator?: ResponseValidator<Response>,
};
type GetRequest<Response = mixed> = HTTPRequest<'GET', Response>;
const defaultValidator: ResponseValidator<mixed> = (data: any) => (data: mixed);
const getRequest= <Response>({
url,
responseValidator = defaultValidator,
}: {
url: string,
responseValidator?: ResponseValidator<Response>,
}): GetRequest<Response> => ({
method: 'GET',
url,
responseValidator,
});
Which results in:结果是:
23: responseValidator = defaultValidator,
^ mixed [1] is incompatible with `Response` [2].
References:
19: const defaultValidator: ResponseValidator<mixed> = (data: any) => (data:
mixed);
^ [1]
6: type ResponseValidator<Response> = (mixed) => Response;
^ [2]
I was thinking maybe I could put a default on the Response
generic of the function, but flow doesn't seem to support defaults on generics for function, and I doubt that would actually work anyway.我在想也许我可以在函数的Response
泛型上设置默认值,但 flow 似乎不支持函数泛型的默认值,我怀疑这是否真的有效。 Is there a better way to approach this?有没有更好的方法来解决这个问题?
Here's what I ended up going with. 这就是我最终的结果。
I basically sidestepped this issue by, fundamentally, being even more explicit with my types.我基本上回避了这个问题,从根本上说,我的类型更加明确。 So now I have two types of request builders, a RequestBuilder
:所以现在我有两种类型的请求构建器,一个RequestBuilder
:
/**
* Union of request builders for different HTTP methods.
*/
export type RequestBuilder<UrlParams, Params, SerializedParams> =
| GetRequestBuilder<UrlParams>
| DeleteRequestBuilder<UrlParams>
| PostRequestBuilder<UrlParams, Params, SerializedParams>
| HeadRequestBuilder<UrlParams, Params, SerializedParams>
;
And a ValidatedRequestBuilder
(maybe this should be "validat ing request builder?" Still some details to work out):和ValidatedRequestBuilder
(也许这应该是“validat荷兰国际集团的要求建设者?”还有一些细节需要处理):
/**
* A RequestBuilder packaged up with a ResponseValidator and a deserializer.
*/
export type ValidatedRequestBuilder<
UrlParams,
Params,
SerializedParams,
RB: RequestBuilder<UrlParams, Params, SerializedParams>,
Response,
Format,
> = {
requestBuilder: RB,
responseValidator: ResponseValidator<Response>,
deserializer: (Response) => Format,
};
And then a union of those two types, AbstractRequestBuilder
.然后是这两种类型的联合, AbstractRequestBuilder
。 You'll see here that this begins to hint at the solution:你会在这里看到这开始暗示解决方案:
/**
* A RequestBuilder which may or may not be a ValidatedRequestBuilder.
*
* This abstracts the API between RequestBuilder and ValidatedRequestBuilder so
* that they can be used interchangeable (this can be used as if it were a
* ValidatedRequestBuilder).
*/
export type AbstractRequestBuilder<
UrlParams,
Params,
SerializedParams,
RB: RequestBuilder<UrlParams, Params, SerializedParams>,
// it's very important that these default to `mixed` for a regular
// `RequestBuilder`, this behavior is relied upon when creating a default
// validator and deserializer for a regular `RequestBuilder`
Response=mixed,
Format=mixed,
> =
| ValidatedRequestBuilder<UrlParams, Params, SerializedParams, RB, Response, Format>
| RB;
So as far as we're concerned, all request builders are AbstractRequestBuilder
s, so when we go to actually build a request from the AbstractRequestBuilder
in question, if the underlying request builder is not a ValidatedRequestBuilder
, we just implement a default validator and deserializer for it that are basically identity functions that return mixed
:所以就我们而言,所有请求构建器都是AbstractRequestBuilder
,所以当我们从有问题的AbstractRequestBuilder
实际构建请求时,如果底层请求构建器不是ValidatedRequestBuilder
,我们只需实现一个默认的验证器和反序列化器它基本上是返回mixed
身份函数:
/**
* Gets a `ValidatedRequest` for the given `AbstractRequestBuilder`,
* `UrlParams`, and body `Params`.
*
* The important thing is that this does the job of differentiating between a
* `RequestBuilder` and a `ValidatedRequestBuilder` and abstracting behavior.
* Basically a `ValidatedRequestBuilder` will have a concrete `Response` and
* `Format`, while a `RequestBuilder` will end up with `mixed`.
*/
export const validatedRequestForBuilder = <
UrlParams,
Params,
SerializedParams: ValidParams,
Response,
Format,
ARB: AbstractRequestBuilder<UrlParams,
Params,
SerializedParams,
RequestBuilder<UrlParams, Params, SerializedParams>,
Response,
Format>,
>(
abstractRequestBuilder: ARB,
urlParams: UrlParams,
params: Params,
): ValidatedRequest<SerializedParams, Request<SerializedParams>, Response, Format> => (
typeof abstractRequestBuilder === 'function'
? {
request: (
abstractRequestBuilder: RequestBuilder<UrlParams,
Params,
SerializedParams>
)(urlParams, params),
responseValidator: data => ((data: any): Response), // Response is always mixed here
deserializer: (data: Response) => ((data: any): Format), // Format is always mixed here
}
: {
request: abstractRequestBuilder.requestBuilder(urlParams, params),
responseValidator: abstractRequestBuilder.responseValidator,
deserializer: abstractRequestBuilder.deserializer,
}
);
So basically every request builder always results in ValidatedRequest
s which guarantee some particular deserialized response type, but in some cases, where we passed a regular RequestBuilder
and not a ValidatedRequestBuilder
, the particular deserialized response type will be mixed
.所以基本上每个请求构建器总是导致ValidatedRequest
s,它保证某些特定的反序列化响应类型,但在某些情况下,我们传递常规RequestBuilder
而不是ValidatedRequestBuilder
,特定的反序列化响应类型将是mixed
。 If we don't want to deal with the mixed
, then we should specify a validator.如果我们不想处理mixed
,那么我们应该指定一个验证器。
So at the core of this there's a pretty standard pattern that involves being nice and explicit with types and using unions to model alternative scenarios rather than things like option types or optional properties.因此,其核心是一个非常标准的模式,它涉及对类型进行良好和明确的处理,并使用联合来建模替代场景,而不是像选项类型或可选属性之类的东西。 Unions are much more explicit.工会更加明确。 I've been thinking about this a lot in terms of things like react prop types.我一直在思考这个问题,比如 react prop 类型。 You might have something like:你可能有这样的事情:
type PriceType = 'wholesale' | 'retail';
type Props = {
label: string,
hasPrice: boolean,
priceType?: PriceType,
};
Where priceType
is required if hasPrice
is true
, but is not relevant if hasPrice
is false.凡priceType
是必需的,如果hasPrice
是true
,但是是不相关的,如果hasPrice
是假的。 So you look at that and say, well, sometimes I will pass priceType
and sometimes I won't so I guess it should be optional.所以你看着它说,好吧,有时我会通过priceType
有时我不会,所以我想它应该是可选的。 But these are actually two totally separate scenarios that require a union to model correctly:但这实际上是两个完全独立的场景,需要联合才能正确建模:
type PriceType = 'wholesale' | 'retail';
type AlwaysProps = $ReadOnly<{|
label: string,
|}>;
type Props = $ReadOnly<{|
...AlwaysProps,
hasPrice: true,
priceType: PriceType,
|}> | $ReadOnly<{|
...AlwaysProps,
hasPrice: false,
|}>;
So I guess the lesson here is when you find yourself using options you should consider whether or not those can or should be more accurately typed as unions.所以我想这里的教训是,当您发现自己使用选项时,您应该考虑这些选项是否可以或应该更准确地键入为联合。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.