Какой лучший способ вывести тип аргумента обратного вызова из типа объединения в Typescript?

Я пытаюсь написать помощника, который позволит мне подписаться на определенный тип отправленных действий в приложении React. Вот интерфейсы действий, с которыми я работаю:

enum ActionTypes {
  MoveToLine = 'MOVE_TO_LINE',
  MoveToColumn = 'MOVE_TO_COLUMN',
}

interface MoveToLineAction {
  type: ActionTypes.MoveToLine;
  payload: { line: number };
}

interface MoveToColumnAction {
  type: ActionTypes.MoveToColumn;
  payload: { column: number };
}

type Actions = MoveToLineAction | MoveToColumnAction;

А вот реализация subscribeToAction:

const subscribeToAction = <A extends { type: string }, T extends ActionTypes>(
  type: T,
  onAction: (a: A) => void,
) => (action: A) => {
  if (action.type === type) {
    onAction(action);
  }
};

Я хочу использовать это так:

// Subscribe to a specific type of action
subscribeToAction(ActionTypes.MoveToLine, action => {
  action.payload.line;
});

Проблема, с которой я сталкиваюсь, заключается в том, что я хочу, чтобы тип action в прослушивателе onAction определялся автоматически. В приведенном выше коде я получаю Property 'payload' does not exist on type '{ type: string; }' ошибку. Я могу вручную ввести слушателя, чтобы обойти это следующим образом:

subscribeToAction(ActionTypes.MoveToLine, (action: MoveToLineAction) => {
  action.payload.line;
});

Но кажется излишним передавать ActionType, а затем также указывать тип действия в слушателе. Есть ли у вас какие-нибудь предложения, как этого избежать?


person eldarshamukhamedov    schedule 06.04.2019    source источник


Ответы (1)


Вот как я бы это сделал:

type ActionOfType<T extends ActionTypes> = Extract<Actions, { type: T }>;

const subscribeToAction = <T extends ActionTypes>(
    type: T,
    onAction: (a: ActionOfType<T>) => void,
) => (action: Actions) => {
    if (action.type === type) {
        // assertion necessary or explicit type guard: 
        onAction(action as ActionOfType<T>);
    }
};

subscribeToAction(ActionTypes.MoveToLine,
    action => { // action inferred as MoveToLineAction
        action.payload.line;
    }
);

На самом деле вам не нужны два общих параметра в subscribeToAction(). Вы хотите, чтобы T соответствовал ActionTypes компоненту, переданному как type, и вы хотите, чтобы аргумент обратного вызова onAction автоматически ограничивался соответствующей составляющей Actions. Функция типа ActionOfType<T> показывает, как это можно сделать. Да, и возвращаемое значение subscribeToAction должно быть функцией, которая принимает any Actions, верно? Вот почему вы делаете if (action.type === type) проверку.

Также обратите внимание, что, поскольку action относится к типу Actions, вам нужно сообщить компилятору, что тест if (action.type === type) фактически сужает action до ActionOfType<T>. Я просто использую утверждение типа.

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

person jcalz    schedule 07.04.2019
comment
Спасибо, похоже, это то, что я ищу. Не знал о Extract. Я попробую. :) - person eldarshamukhamedov; 07.04.2019