基于函数参数的可选泛型

时间:2019-02-27 09:58:59

标签: javascript flowtype

我想通过JSON模式验证XHR请求。我对每种响应类型都有验证功能。如果指定了验证功能,则应从验证功能中提取XHR处理程序的响应类型。如果未指定验证功能,则我希望响应类型为mixed,以便必须处理未知的响应数据。

所以我有这个:

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,
  });

这将导致:

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]

Try Link

我当时想也许可以在函数的Response泛型上设置默认值,但是流程似乎不支持该函数的泛型上的默认值,并且我怀疑这种方法是否仍然有效。有更好的方法来解决这个问题吗?

1 个答案:

答案 0 :(得分:1)

Here's what I ended up going with.

从根本上讲,我对我的类型更加明确,从而回避了这个问题。所以现在我有两种类型的请求构建器,一个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>
;

还有一个ValidatedRequestBuilder(也许应该是“ validat ing 请求构建器?”还有一些细节需要解决):

/**
 * 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,
};

然后是AbstractRequestBuilder这两种类型的并集。您会在这里看到这开始暗示解决方案:

/**
 * 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;

就我们而言,所有请求构建器都是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, } ); 来保证某种特定的反序列化响应类型,但是在某些情况下,我们传递了常规的ValidatedRequest而不是RequestBuilder,特定的反序列化响应类型将为ValidatedRequestBuilder。如果我们不想处理mixed,则应指定一个验证器。

因此,在此过程的核心是一个漂亮的标准模式,该模式涉及对类型进行友好和显式说明,并使用联合对替代方案(而不是选项类型或可选属性)进行建模。工会要明确得多。我一直在考虑诸如反应道具类型之类的事情。您可能会有类似的内容:

mixed

如果type PriceType = 'wholesale' | 'retail'; type Props = { label: string, hasPrice: boolean, priceType?: PriceType, }; priceType,则hasPrice是必需的;如果true为假,则无关。因此,您看了一下,然后说,好吧,有时候我会通过hasPrice,有时候我不会,所以我想它应该是可选的。但是实际上,这是两个完全独立的场景,需要联合才能正确建模:

priceType

所以我想这里的教训是,当您发现自己使用选项时,应该考虑这些选项是否可以或应该更准确地键入为并集。