Redux + Typescript:参数类型 'action' 和 'action' 不兼容
Redux + Typescript: Types of parameters 'action' and 'action' are incompatible
我遇到了一个恼人的案例,无法弄清楚为什么 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>'
为什么它会尝试将一个接口分配给另一个接口(InvalidateMembers
到 InvalidateCatgories
,反之亦然)?我可以摆脱错误的唯一方法是在如下接口中将 'type' 的类型更改为字符串(因此两个接口具有相同的结构):
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;
}
};
问题
您看到的行为是设计使然。想想 combineReducers
创建的根减速器实际上做了什么。它获得状态和动作。然后,为了更新状态的每一部分,它使用操作调用该部分的缩减器。这意味着 每个减速器接收每个动作 .
当通过成员操作调用类别缩减器时,它什么都不做(情况 default:
)并且 returns 现有类别状态不变。但是你的类型必须允许减速器 category
被类型 MemberAction
的动作调用,因为它 将 被这些动作调用。
解决方案
更新您的类型,以便每个 reducer 都可以接受所有可能的操作的联合,您已经将其定义为 RootActions
。
export const member: Reducer<MemberState, RootActions>
export const category: Reducer<CategoryState, RootActions>
备选
如果出于某种原因您坚持每个减速器应该只能采用它自己的操作类型,那是可能的,但需要更多的工作。您需要做的是编写您自己的 combineReducers
,它检查 action.type
以查看它是 MemberAction
还是 CategoryAction
。它不会将选项传递给所有 reducer,它只会调用与操作匹配的 reducer 并假设其余部分没有变化。
我遇到了一个恼人的案例,无法弄清楚为什么 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>'
为什么它会尝试将一个接口分配给另一个接口(InvalidateMembers
到 InvalidateCatgories
,反之亦然)?我可以摆脱错误的唯一方法是在如下接口中将 'type' 的类型更改为字符串(因此两个接口具有相同的结构):
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;
}
};
问题
您看到的行为是设计使然。想想 combineReducers
创建的根减速器实际上做了什么。它获得状态和动作。然后,为了更新状态的每一部分,它使用操作调用该部分的缩减器。这意味着 每个减速器接收每个动作 .
当通过成员操作调用类别缩减器时,它什么都不做(情况 default:
)并且 returns 现有类别状态不变。但是你的类型必须允许减速器 category
被类型 MemberAction
的动作调用,因为它 将 被这些动作调用。
解决方案
更新您的类型,以便每个 reducer 都可以接受所有可能的操作的联合,您已经将其定义为 RootActions
。
export const member: Reducer<MemberState, RootActions>
export const category: Reducer<CategoryState, RootActions>
备选
如果出于某种原因您坚持每个减速器应该只能采用它自己的操作类型,那是可能的,但需要更多的工作。您需要做的是编写您自己的 combineReducers
,它检查 action.type
以查看它是 MemberAction
还是 CategoryAction
。它不会将选项传递给所有 reducer,它只会调用与操作匹配的 reducer 并假设其余部分没有变化。