如何修复切片与 RootState 的循环依赖?
How to fix circular dependencies of slices with the RootState?
我最近开始使用 redux-toolkit 并开始使用 createSlice
按照他们的文档编写我的减速器。
一个reducer,我们称它为reducerA
,导入customAsyncFunction
来处理它的回调,这个函数是通过createAsyncThunk
创建的,它又在它时读取RootState
调用 thunkApi.getState()
,现在的问题是当导入 RootReducer
时,将导入 reducerA
生成循环引用。
基本上:RootReducer -> reducerA -> actions -> RootReducer -> ...
下面我尝试简化问题。
// actions.ts file
import { RootState } from "./RootReducer";
export const customAsyncAction = createAsyncAction("myaction", async (_, thunkApi) =>
const state = thinkApi.getState() as RootState;
...
);
// reducerA.ts file
import { customAsyncAction } from "./actions";
const slice = createSlice({
...
extraReducers: {
[customAsyncAction.fulfilled.toString()]: ... // handles fulfilled action
}
});
export default slice.reducer;
// RootReducer.ts file
import reducerA from "./reducerA"
import reducerB from "./reducerB"
const reducers = combineReducers({
reducerA,
reducerB
});
export type RootState = ReturnType<typeof reducers>; // complains about circular reference
在 this section of the documentation 中提到了这种情况发生的可能性,并且模糊地建议将代码拆分到文件中。然而,从我所有的尝试中,我似乎无法找到解决这个问题的方法。
Type-only 循环引用没问题。 TS 编译器将在编译时解决这些问题。特别是,切片文件导出其缩减器,将缩减器导入商店设置,根据该切片定义 RootState
类型,然后 re-import 返回 RootState
类型是正常的放入切片文件。
只有在涉及运行时行为时,循环导入才是一个潜在的问题,例如两个切片依赖于彼此的操作。
不幸的是,据我所知,用于捕获循环依赖项的 ESLint 规则无法判断正在导入的内容只是一种类型。
在阅读了关于如何将 createAsyncThunk
与 TypeScript 结合使用的 following docs section 之后,我将 extraReducers
的实现更改为使用 builder
模式而不是传递一个键值对象和错误消失。
// before
const slice = createSlice({
...
extraReducers: {
[customAsyncAction.fulfilled.toString()]: ... // handles fulfilled action
}
});
// after
const slice = createSlice({
...
extraReducers: builder => {
builder.addCase(customAsyncAction.fulfilled, (state, action) => ...)
}
});
我必须承认,我无法准确指出为什么在第一种情况下它不起作用,而在第二种情况下却起作用。
Babel 认为 import 是一个模块,并不知道它是一个类型,完全可以导入它。为了告诉它是一个类型导入尝试将它作为一个类型导入:
import type { RootState } from "./RootReducer";
注意 import
之后的 type
关键字。这样 babel/eslint 知道您导入的是类型,而不是模块,并将其从依赖映射中排除,从而解决问题。
我最近开始使用 redux-toolkit 并开始使用 createSlice
按照他们的文档编写我的减速器。
一个reducer,我们称它为reducerA
,导入customAsyncFunction
来处理它的回调,这个函数是通过createAsyncThunk
创建的,它又在它时读取RootState
调用 thunkApi.getState()
,现在的问题是当导入 RootReducer
时,将导入 reducerA
生成循环引用。
基本上:RootReducer -> reducerA -> actions -> RootReducer -> ...
下面我尝试简化问题。
// actions.ts file
import { RootState } from "./RootReducer";
export const customAsyncAction = createAsyncAction("myaction", async (_, thunkApi) =>
const state = thinkApi.getState() as RootState;
...
);
// reducerA.ts file
import { customAsyncAction } from "./actions";
const slice = createSlice({
...
extraReducers: {
[customAsyncAction.fulfilled.toString()]: ... // handles fulfilled action
}
});
export default slice.reducer;
// RootReducer.ts file
import reducerA from "./reducerA"
import reducerB from "./reducerB"
const reducers = combineReducers({
reducerA,
reducerB
});
export type RootState = ReturnType<typeof reducers>; // complains about circular reference
在 this section of the documentation 中提到了这种情况发生的可能性,并且模糊地建议将代码拆分到文件中。然而,从我所有的尝试中,我似乎无法找到解决这个问题的方法。
Type-only 循环引用没问题。 TS 编译器将在编译时解决这些问题。特别是,切片文件导出其缩减器,将缩减器导入商店设置,根据该切片定义 RootState
类型,然后 re-import 返回 RootState
类型是正常的放入切片文件。
只有在涉及运行时行为时,循环导入才是一个潜在的问题,例如两个切片依赖于彼此的操作。
不幸的是,据我所知,用于捕获循环依赖项的 ESLint 规则无法判断正在导入的内容只是一种类型。
在阅读了关于如何将 createAsyncThunk
与 TypeScript 结合使用的 following docs section 之后,我将 extraReducers
的实现更改为使用 builder
模式而不是传递一个键值对象和错误消失。
// before
const slice = createSlice({
...
extraReducers: {
[customAsyncAction.fulfilled.toString()]: ... // handles fulfilled action
}
});
// after
const slice = createSlice({
...
extraReducers: builder => {
builder.addCase(customAsyncAction.fulfilled, (state, action) => ...)
}
});
我必须承认,我无法准确指出为什么在第一种情况下它不起作用,而在第二种情况下却起作用。
Babel 认为 import 是一个模块,并不知道它是一个类型,完全可以导入它。为了告诉它是一个类型导入尝试将它作为一个类型导入:
import type { RootState } from "./RootReducer";
注意 import
之后的 type
关键字。这样 babel/eslint 知道您导入的是类型,而不是模块,并将其从依赖映射中排除,从而解决问题。