Redux + Typescript:参数类型'action'和'action'不兼容

时间:2018-12-20 09:55:16

标签: typescript redux interface typescript-typings redux-thunk

我这里有一个烦人的案例,无法弄清楚TS为什么会引发波纹管错误:

src/store.ts:24:3 - error TS2322: Type 'Reducer<MemberState, InvalidateMembers>' is not assignable to type 'Reducer<MemberState, RootActions>'.
  Types of parameters 'action' and 'action' are incompatible.
    Type 'RootActions' is not assignable to type 'InvalidateMembers'.
      Type 'InvalidateCatgories' is not assignable to type 'InvalidateMembers'.

24   member,
     ~~~~~~

  src/store.ts:18:3
    18   member: MemberState;
         ~~~~~~
    The expected type comes from property 'member' which is declared here on type 'ReducersMapObject<RootState, RootActions>'

src/store.ts:25:3 - error TS2322: Type 'Reducer<CategoryState, InvalidateCatgories>' is not assignable to type 'Reducer<CategoryState, RootActions>'.
  Types of parameters 'action' and 'action' are incompatible.
    Type 'RootActions' is not assignable to type 'InvalidateCatgories'.
      Type 'InvalidateMembers' is not assignable to type 'InvalidateCatgories'.

25   category,
     ~~~~~~~~

  src/store.ts:19:3
    19   category: CategoryState;
         ~~~~~~~~
    The expected type comes from property 'category' which is declared here on type 'ReducersMapObject<RootState, RootActions>'

为什么它尝试将一个接口分配给另一个接口(InvalidateMembersInvalidateCatgories,反之亦然)?我摆脱错误的唯一方法是在以下接口中将“类型”的类型更改为字符串(这样两个接口具有相同的结构):

interface InvalidateMembers extends Action {
  type: string;
}

它让我非常困惑。我已经对所有内容进行了三重检查,并检查了所有的redux类型,但不明白为什么会出错。

-更新:-

再检查一下redux类型后,我意识到ReducersMapObject沿着整个rootReducer对象带回RootActions的每个属性,这显然不会再匹配一个属性。我认为这更多是类型本身设计的问题,或者?

export type Reducer<S = any, A extends Action = AnyAction> = (
  state: S | undefined,
  action: A
) => S

/**
 * Object whose values correspond to different reducer functions.
 *
 * @template A The type of actions the reducers can potentially respond to.
 */
export type ReducersMapObject<S = any, A extends Action = Action> = {
  [K in keyof S]: Reducer<S[K], A>
}

非常感谢您的反馈。

store.js

...

export interface RootState {
  member: MemberState;
  category: CategoryState;
}
export type RootActions = MemberAction | CategoryAction;

const rootReducer = combineReducers<RootState, RootActions>({
  member,
  category,
});

export const store = createStore(
  rootReducer,
  composeWithDevTools(applyMiddleware(thunk as ThunkMiddleware<RootState, RootActions>))
);

actions / member.js

export enum MemberActionTypes {
  INVALIDATE_MEMBERS = 'INVALIDATE_MEMBERS'
}

interface InvalidateMembers extends Action {
  type: MemberActionTypes.INVALIDATE_MEMBERS;
}

export const invalidateMembers = (): ThunkResult<void> => (dispatch) => {
  dispatch({
    type: MemberActionTypes.INVALIDATE_MEMBERS
  });
};

export type MemberAction = InvalidateMembers;

actions / category.js

export enum CategoryActionTypes {
  INVALIDATE_CATEGORIES = 'INVALIDATE_CATEGORIES'
}

interface InvalidateCatgories extends Action {
  type: CategoryActionTypes.INVALIDATE_CATEGORIES;
}

export const invalidateCategories = (): ThunkResult<void> => (dispatch) => {
  dispatch({
    type: CategoryActionTypes.INVALIDATE_CATEGORIES
  });
};

export type CategoryAction = InvalidateCatgories;

reducers / member.js

export interface MemberState {
  items: {};
}

const initialState = {
  items: {}
};

export const member: Reducer<MemberState, MemberAction> = (state = initialState, action) => {
  switch (action.type) {
    case MemberActionTypes.INVALIDATE_MEMBERS:
      return {
        ...state,
        didInvalidate: true
      };
    default:
      return state;
  }
};

reducers / category.js

export interface CategoryState {
  items: {};
}

const initialState = {
  items: {},
};

export const category: Reducer<CategoryState, CategoryAction> = (state = initialState, action) => {
  switch (action.type) {
    case CategoryActionTypes.INVALIDATE_CATEGORIES:
      return {
        ...state,
        didInvalidate: true
      };
    default:
      return state;
  }
};

1 个答案:

答案 0 :(得分:2)

问题

您看到的行为是设计使然。考虑一下combineReducers创建的根减速器的实际作用。它得到状态和动作。然后,为了更新状态的每一部分,它使用动作调用该部分的化简器。这就是说每个减速器都会收到每个动作

当通过类成员动作调用类别归约器时,它只是不执行任何操作(情况default:),并不变地返回现有的类别状态。但是您的类型必须允许使用类型为category的操作来调用化简器MemberAction,因为它会被这些操作调用。

解决方案

更新您的类型,以便每个化简器可以接受已定义为RootActions的所有可能动作的并集。

export const member: Reducer<MemberState, RootActions>
export const category: Reducer<CategoryState, RootActions>

替代

如果出于某种原因您坚持认为每个化简只能使用自己的操作类型,那是可能的,但这需要更多的工作。您需要做的是编写自己的combineReducers来检查action.type以查看它是MemberAction还是CategoryAction。与其将选项传递给所有化简器,不如只调用与操作匹配的化简器,并假定其余部分不做任何更改。