Как написать типизированное действие без параметров - PullRequest
0 голосов
/ 17 апреля 2020

Проблема Github предполагает, что мы можем использовать TypedAction.defineWithoutPayload для этой цели, но я не смог найти соответствующих примеров того, как это сделать.

Я использую это для входа в систему, когда accessToken хранится в полезная нагрузка. Если токен присутствует, пользователь получает доступ к личным страницам.

export const login = (token: string) => typedAction("LOGIN", token);

Теперь, на кнопке выхода из системы, я пытаюсь реализовать действие, которое удаляет сохраненное значение в полезной нагрузке. В этом случае не будет параметров для отправки действия. Итак, как мне написать typedAction?

Если я использую:

export const logout = () => typedAction("LOGOUT");

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

Вот мой редуктор:

export const tokenReducer = (
  state: IState['token'] = null,
  { type, payload }: AppAction,
): typeof state => {
  switch (type) {
    case 'LOGIN':
      return payload;
      case 'LOGOUT':
        return null;
    default:
      return state;
  }
};

Codesandbox: https://codesandbox.io/s/keen-brook-kntkm?file= / src / store / actions / login.ts: 50-118

Электронная почта: c@c.com Пароль: проверьте

Редактировать:

export interface IState {
    token: string | null;
  }
const initialState: IState = {
  token: null
};

Ошибка на action.payload, если я использую state: typeof initialState или state = initialState согласно предложению IDE:

Type 'string' is not assignable to type 'IState'.ts(2322)

Если я попытаюсь state: initialState, тогда очевидно:

'initialState' refers to a value, but is being used as a type here. Did you mean 'typeof initialState'?ts(2749)
``

1 Ответ

1 голос
/ 17 апреля 2020

То, как вы определили свою функцию typedAction, работает нормально:

export function typedAction<T extends string>(type: T): { type: T };
export function typedAction<T extends string, P extends any>(
  type: T,
  payload: P
): { type: T; payload: P };
export function typedAction(type: string, payload?: any) {
  return { type, payload };
}

Проблема, которую вы испытываете, заключается в разрушении действия в параметрах вашего редуктора:

export const tokenReducer = (
  state: IState["token"] = null,
  { type, payload }: AppAction
): typeof state => {
  // ...
};

Одна из трудностей с деструктуризацией и TypeScript заключается в том, что после этого тип переменных становится независимым друг от друга. Разбиение действия на { payload, type } создает переменные type: 'LOGIN' | 'LOGOUT' и payload: string | undefined. Даже если вы позже уточните значение type, как в вашем операторе switch, payload все равно будет иметь тип string | undefined; TypeScript не будет автоматически уточнять тип payload в блоке case после уточнения type; их наборы полностью независимы.

Таким образом, довольно уродливый хак, который вы можете использовать, заключается в том, чтобы не деструктировать:

export const tokenReducer = (
  state: IState['token'] = null,
  action: AppAction,
): typeof state => {
  switch (action.type) {
    case 'LOGIN':
      return action.payload;
    case 'LOGOUT':
      return null;
    default:
      return state;
  }
};

Это работает, потому что в вашем операторе switch он может улучшить action: AppAction введите более специфичные c типы входа или выхода, так что action.payload теперь сильно привязан к типу полезной нагрузки, указанному c для одного из этих действий.

Вот альтернативный шаблон для редукционных действий, которые я использую что вы можете найти более удобным на моей вилке , который позволяет вам наслаждаться мощью отображаемых типов для определения редукторов с меньшим количеством шаблонов. Во-первых, вы должны определить тип с сопоставлениями типа / полезной нагрузки и определить некоторые типы, вытекающие из этого:

export type ActionPayloads = {
  LOGIN: string;
  LOGOUT: void;
};

export type ActionType = keyof ActionPayloads;

export type Action<T extends ActionType> = {
  type: T;
  payload: ActionPayloads[T];
};

Ваши создатели действий теперь могут быть определены в терминах этой карты:

export function typedAction<T extends ActionType>(
  type: T,
  payload: ActionPayloads[T]
) {
  return { type, payload };
}

Далее вы можете определить вспомогательную функцию для создания строго типизированного редуктора:

type ReducerMethods<State> = {
  [K in ActionType]?: (state: State, payload: ActionPayloads[K]) => State
};

type Reducer<State> = (state: State, action: AppAction) => State;

function reducer<State>(
  initialState: State,
  methods: ReducerMethods<State>
): Reducer<State> {
  return (state: State = initialState, action: AppAction) => {
    const handler: any = methods[action.type];
    return handler ? handler(state, action.payload) : state;
  };
}

(я не нашел хорошего обходного пути для этого уродливого : any приведения, но по крайней мере мы знаем логически что печатание является звуковым извне).

Теперь вы можете определять свои редукторы, таким образом, с помощью неявной типизации для ваших обработчиков действий:

type TokenState = string | null;

export const tokenReducer = reducer<TokenState>(null, {
  LOGIN: (state, token) => token, // `token` is implicitly typed as `string`
  LOGOUT: () => null              // TS knows that the payload is `undefined`
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...