Правильное использование дженериков TypeScript

#typescript #typescript-generics

#typescript #typescript-дженерики

Вопрос:

Моя цель здесь — сгруппировать запросы и ответы по действию, поэтому, если у меня есть (как в приведенном ниже коде) два действия: Play и Stay , у меня будет пара запрос / ответ, представляющая запрос / ответ Play действия и то же самое для Stay действия.

Я хочу выразить ограничение, заключающееся в том, что могут быть связаны только запрос и ответ на одно и то же действие (см. Playground)

 type ActionName = "Play" | "Stay";

type RequestD<A extends ActionName, B extends Record<string, unknown>> = {
  _tag: "Request";
  _action: A;
} amp; B;

type ResponseD<A extends ActionName, B extends Record<string, unknown>> = {
  _tag: "Response";
  _action: A;
} amp; B;

type PlayRequest = RequestD<"Play", { a: number }>;
type PlayResponse = ResponseD<"Play", { b: number }>;

type StayRequest = RequestD<"Stay", { c: number }>;
type StayResponse = ResponseD<"Stay", { d: number }>;

type ActionOf<X> = X extends RequestD<infer A, any>
  ? A
  : X extends ResponseD<infer A, any>
  ? A
  : never;

// Problem:
// X is "Play" | "Stay"`
// Is there a way to get "Play"?
type X = ActionOf<PlayRequest>;

type TagOf<X> = X extends RequestD<any, any>
  ? "Request"
  : X extends ResponseD<any, any>
  ? "Response"
  : never;

// Problem:
// Y1 is "Request" and that's expected
// Y2 is "Request" as well and that's unexpected, how is this possible? How can I fix it?
type Y1 = TagOf<PlayRequest>;
type Y2 = TagOf<PlayResponse>;

type RoundTrip<
  A extends ActionName,
  Req extends RequestD<A, any>,
  Res extends ResponseD<A, any>
> = {
  _action: A;
  request: Req;
  response: Res;
};

type PlayRT = RoundTrip<"Play", PlayRequest, PlayResponse>;

// Problem:
// The following should be wrong because the first request is related to action "Stay", not "Play"
// Is there a way to enforce that?
type WrongRT = RoundTrip<"Play", StayRequest, PlayResponse>;
 

Мои вопросы, изложенные в приведенном выше коде:

  1. Как я могу связать действие «Воспроизвести» с запросом / ответом, чтобы использовать его на уровне типа?
  2. Почему я не могу различать запрос / ответ, даже если эти типы соответствующим образом помечены ( _tag поле)?
  3. Как я могу ограничить действие, чтобы оно было тем же действием в запросе / ответе по RoundTrip типу?

Ответ №1:

Проблема на самом деле довольно проста, вы используете any в качестве второго параметра в Request и Response .

Если вы посмотрите на свое определение:

 type RequestD<A extends ActionName, B extends Record<string, unknown>> = {
  _tag: "Request";
  _action: A;
} amp; B; // note that you are extending the entire object.
 

Затем, когда вы затем определите дженерик как таковой:

 type TagOf<X> = X extends RequestD<any, any>
  ? "Request"
  : X extends ResponseD<any, any>
  ? "Response"
  : never;
 

Это приведет к сбою, поскольку any включает объект, например, { _tag: "Request" } для ответа или { _tag: "Response" } для запроса.

Это в основном не дает typescript ничего для работы. Чтобы исправить это, вам просто нужно изменить any на что-то вроде пустого объекта {} .

Игровая площадка

Комментарии:

1. Спасибо! Это ответ на вопрос номер 2, есть какие-либо подсказки по другим?

2. Подождите, это также устраняет проблему № 3, остается только номер 1, какие-либо предложения?

3. Неважно, вы правы во всех отношениях, используя RequestD<any, {}> и ResponseD<any, {}> , ActionOf я получаю правильный ответ. Спасибо!