Redux + Typescript:参数“action”和“action”的类型不兼容

Edm*_*mas 6 interface typescript redux redux-thunk typescript-typings

我在这里有一个烦人的案例,无法弄清楚为什么 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>'
Run Code Online (Sandbox Code Playgroud)

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

interface InvalidateMembers extends Action {
  type: string;
}
Run Code Online (Sandbox Code Playgroud)

它让我非常困惑。我已经三重检查了所有内容 + 检查了所有 redux 类型,但无法理解错误的原因。

- 更新: -

在对 redux 类型进行了更多检查后,我意识到将整个对象的ReducersMapObject每个属性都带回rootReducerRootActions一个,这显然不再匹配单个属性。我认为这更多是类型本身的设计问题,还是?

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>
}
Run Code Online (Sandbox Code Playgroud)

我非常感谢您的反馈。

商店.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>))
);
Run Code Online (Sandbox Code Playgroud)

动作/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;
Run Code Online (Sandbox Code Playgroud)

动作/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;
Run Code Online (Sandbox Code Playgroud)

减速器/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;
  }
};
Run Code Online (Sandbox Code Playgroud)

减速器/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;
  }
};
Run Code Online (Sandbox Code Playgroud)

Lin*_*ste 2

问题

您所看到的行为是设计使然的。想想创建的根减速器combineReducers实际上做了什么。它有一个状态和一个动作。然后,为了更新状态的每个部分,它使用该操作调用该部分的减速器。这意味着每个减速器都会收到每个动作

当使用成员操作调用类别缩减器时,它只是不执行任何操作(case default:)并返回现有类别状态不变。category但是您的类型必须允许使用类型的操作来调用减速器,MemberAction因为它将通过这些操作来调用。

解决方案

更新您的类型,以便每个减速器可以接受所有可能操作的并集,您已经将其定义为RootActions.

export const member: Reducer<MemberState, RootActions>
export const category: Reducer<CategoryState, RootActions>
Run Code Online (Sandbox Code Playgroud)

选择

如果由于某种原因你坚持认为每个减速器应该只能采取它自己的操作类型,这是可能的,但它需要更多的工作。您需要编写自己的代码combineReducers来检查action.type它是否是 aMemberAction或 a CategoryAction。它不会将选项传递给所有减速器,而是只会调用与操作匹配的减速器,并假设其余部分没有更改。