繁体   English   中英

基于函数参数的可选泛型

[英]Optional Generic Based On Function Argument

我想通过 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]

尝试链接

我在想也许我可以在函数的Response泛型上设置默认值,但 flow 似乎不支持函数泛型的默认值,我怀疑这是否真的有效。 有没有更好的方法来解决这个问题?

这就是我最终的结果。

我基本上回避了这个问题,从根本上说,我的类型更加明确。 所以现在我有两种类型的请求构建器,一个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荷兰国际集团的要求建设者?”还有一些细节需要处理):

/**
 * 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 s,它保证某些特定的反序列化响应类型,但在某些情况下,我们传递常规RequestBuilder而不是ValidatedRequestBuilder ,特定的反序列化响应类型将是mixed 如果我们不想处理mixed ,那么我们应该指定一个验证器。

因此,其核心是一个非常标准的模式,它涉及对类型进行良好和明确的处理,并使用联合来建模替代场景,而不是像选项类型或可选属性之类的东西。 工会更加明确。 我一直在思考这个问题,比如 react prop 类型。 你可能有这样的事情:

type PriceType = 'wholesale' | 'retail';

type Props = {
    label: string,
    hasPrice: boolean,
    priceType?: PriceType,
};

priceType是必需的,如果hasPricetrue ,但是是不相关的,如果hasPrice是假的。 所以你看着它说,好吧,有时我会通过priceType有时我不会,所以我想它应该是可选的。 但这实际上是两个完全独立的场景,需要联合才能正确建模:

type PriceType = 'wholesale' | 'retail';

type AlwaysProps = $ReadOnly<{|
  label: string,
|}>;

type Props = $ReadOnly<{|
    ...AlwaysProps,
    hasPrice: true,
    priceType: PriceType,
|}> | $ReadOnly<{|
  ...AlwaysProps,
  hasPrice: false,
|}>;

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM