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

Posted

技术标签:

【中文标题】Redux + Typescript:参数\'action\'和\'action\'的类型不兼容【英文标题】:Redux + Typescript: Types of parameters 'action' and 'action' are incompatibleRedux + Typescript:参数'action'和'action'的类型不兼容 【发布时间】:2019-05-20 19:54:34 【问题描述】:

我这里有一个烦人的情况,无法弄清楚为什么 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>'

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

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;
  
;

【问题讨论】:

你能试试把RootActions改成AnyAction这里const rootReducer = combineReducers&lt;RootState, RootActions&gt;吗? 【参考方案1】:

问题

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

当使用成员操作调用类别缩减器时,它什么也不做(案例default:)并返回现有类别状态不变。但是您的类型必须允许使用 MemberAction 类型的操作调用减速器 category,因为它通过这些操作被调用。

解决方案

更新您的类型,以便每个 reducer 可以接受所有可能的操作的联合,您已经将其定义为 RootActions

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

另类

如果出于某种原因你坚持每个 reducer 应该只能采用它自己的动作类型,那是可能的,但它需要更多的工作。你需要做的是写你自己的combineReducers,它检查action.type,看看它是MemberAction还是CategoryAction。它不会将选项传递给所有 reducer,而是只调用与 action 匹配的 reducer,并假设其余部分没有变化。

【讨论】:

以上是关于Redux + Typescript:参数'action'和'action'的类型不兼容的主要内容,如果未能解决你的问题,请参考以下文章

React + Redux-Observable + Typescript - 编译,参数不可分配错误

Typescript React/Redux:“typeof MyClass”类型的参数不可分配给“ComponentType<...”类型的参数

带有 Typescript 和 react-redux 的有状态组件:“typeof MyClass”类型的参数不可分配给 Component 类型的参数

如何在 TypeScript 中键入 Redux 操作和 Redux reducer?

强烈键入 react-redux 与 typescript 连接

StoreEnhancer 的 TypeScript 定义阻止了剩余参数