Объедините универсальные интерфейсы с двумя универсальными типами в один тип только с одним универсальным типом

В моем случае я работаю с использованиередьюсер из React, но мой вопрос касается чисто машинописного текста.

Возьмите следующее:

export interface State<T> {
  values: T;
  someOtherProp: boolean;
}

export interface ActionOne<T, K extends keyof T> {
  type: "UPDATE_VALUE_ONE";
  payload: { name: K; value: T[K] };
}


export interface ActionTwo<T, K extends keyof T> {
  type: "UPDATE_VALUE_TWO";
  payload: { name: K; value: T[K] };
}

Теперь два интерфейса действий объединены в один тип объединения:

type ActionTypes<T, K extends keyof T> = ActionOne<T, K> | ActionTwo<T, K>

На следующем шаге функция будет использовать ActionTypes в качестве типа своего параметра:

export const reducer = <T>(
  state: State<T>,
  action: ActionTypes<T> // This already breaks because the second generic type is missing
): State<T> =>
{ 
   switch (action.type) {
     case "UPDATE_VALUE_ONE":
      return {
        ...state,
        values: { ...state.values, [action.payload.name]: action.payload.value}
      };
     case "UPDATE_VALUE_ONE":
    // ...
  }
}

Как вы можете видеть в комментарии к коду, ActionTypes<T> уже ломается. В моем случае я не хочу, чтобы функция reducer беспокоилась о том, какой тип K у ActionTypes<T, K>.

Я знаю, что могу добавить keyof T к action: ActionTypes<T, keyof T>, но это нарушит проверку типов для этой строки:

values: { ...state.values, [action.payload.name]: action.payload.value}

потому что тип [action.payload.name] может отличаться от action.payload.value

На самом деле было бы даже лучше, если бы ActionType<T, K extends keyof T> имел только один общий тип (T).

Если бы ActionOne и ActionTwo возвращались функциями вместо того, чтобы передавать их напрямую как объект, это работало бы, объявляя встроенные функции:

type ActionTypes<T> =
 | (<T, K extends keyof T>() => ActionOne<T, K>)
 | (<T, K extends keyof T>() => ActionTwo<T, K>);`

Вопрос

Как информация второго универсального типа K интерфейса может быть «скрыта», когда она используется в другом интерфейсе/типе, который объявляет только один универсальный тип T? (Особенно, если K в любом случае зависит от T посредством K extends keyof T.)

Для универсальных функций это можно решить с помощью встроенного объявления, так как же это можно решить для интерфейсов?

тип ActionTypes<T> = IActionOne<T, keyof T> | IActionTwo<T, ключ T>;

Ali Habibzadeh 29.07.2019 00:20

как я уже сказал в своем посте, это не поможет, потому что нарушит проверку типов..

ysfaran 29.07.2019 08:31
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
0
2
773
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В этом случае кажется, что вы хотели бы, чтобы параметр action был объединением всех возможных типов действий... для каждого возможного K в keyof T. Если это так, я бы использовал распределительный условный тип, чтобы разбить объединение keyof T на каждый отдельный литерал K и получить объединение всех ActionTypes<T, K>:

type SomeActionOne<T, K extends keyof T = keyof T> = K extends any
  ? ActionOne<T, K>
  : never;
type SomeActionTwo<T, K extends keyof T = keyof T> = K extends any
  ? ActionTwo<T, K>
  : never;
type AllPossibleActionTypes<T> = SomeActionOne<T> | SomeActionTwo<T>;

(ОБНОВЛЕНИЕ: я изменил приведенную выше версию AllPossibleActionTypes<T>, чтобы распределить ActionOne<T, K> для всех K, а затем отдельно распределить ActionTwo<T, K> для всех K, а затем унифицировать их. Обратите внимание, что это точно такой же тип, когда вы указываете конкретный тип T, но когда T все еще неразрешенный дженерик, компилятор рассуждает о них по-разному. В частности, в старой версии компилятор ждал, пока он не знал T, чтобы разделить типы на ActionOne и ActionTwo разновидности; теперь компилятор знает это с самого начала.)

Тогда подпись reducer будет:

export const reducer = <T>(
  state: State<T>,
  action: AllPossibleActionTypes<T>
): State<T> => { ... }

И вы могли видеть, что вам разумно намекают, когда вы звоните reducer():

reducer(
  { values: { a: "a", b: 1, c: true }, someOtherProp: true },
  { type: "UPDATE_VALUE_TWO", payload: { name: "b", value: 3 } }
); // hinted for value: number when you type in "b" for name

Хорошо, надеюсь, это поможет; удачи!

Ссылка на код

спасибо за ваш подробный ответ! К сожалению, это не работает, поскольку ActionOne и ActionTwo имеют разные структуры, например. добавить новое свойство полезной нагрузки ActionOne: payload: { name: K; value: T[K], test: string };. В reducer() свойство action.test в случае, если UPDATE_VALUE_ONE не будет существовать. Так что да, для точного примера, который вы привели, это работает, но, к сожалению, не в целом...

ysfaran 29.07.2019 08:29

Я не ожидал обобщения в этом направлении, извините. Я отредактирую свой ответ, чтобы он работал в этом случае.

jcalz 29.07.2019 18:26

извините за поздний ответ .. Я только что попробовал, и он работает так, как хотел, спасибо! Тем не менее, это кажется немного запутанным, потому что вам нужно объявить новый type для каждого действия, которое зависит от K (в моем случае это больше, чем просто два действия..) Мне действительно интересно, нет ли лучшей альтернативы

ysfaran 25.08.2019 10:46

Другие вопросы по теме