В моем случае я работаю с использованиередьюсер из 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.)
Для универсальных функций это можно решить с помощью встроенного объявления, так как же это можно решить для интерфейсов?
как я уже сказал в своем посте, это не поможет, потому что нарушит проверку типов..





В этом случае кажется, что вы хотели бы, чтобы параметр 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 не будет существовать. Так что да, для точного примера, который вы привели, это работает, но, к сожалению, не в целом...
Я не ожидал обобщения в этом направлении, извините. Я отредактирую свой ответ, чтобы он работал в этом случае.
извините за поздний ответ .. Я только что попробовал, и он работает так, как хотел, спасибо! Тем не менее, это кажется немного запутанным, потому что вам нужно объявить новый type для каждого действия, которое зависит от K (в моем случае это больше, чем просто два действия..) Мне действительно интересно, нет ли лучшей альтернативы
тип ActionTypes<T> = IActionOne<T, keyof T> | IActionTwo<T, ключ T>;