[英]How do I use Redux Toolkit with multiple React App instances?
We have written a React app using Redux via Redux Toolkit.我们已经通过 Redux Toolkit 使用 Redux 编写了一个 React 应用程序。 So far so fine.
到目前为止一切顺利。 Now the React app shall be rendered into multiple different elements (each element shall get a new app instance) on the same page.
现在 React 应用程序将在同一页面上呈现为多个不同的元素(每个元素应获得一个新的应用程序实例)。 The rendering part is straight forward: We just call
ReactDOM.render(...)
for each element.渲染部分很简单:我们只是为每个元素调用
ReactDOM.render(...)
。 The Redux part again brings some headache. Redux 部分再次让人头疼。 To create a new Redux store instance for each app instance, we call the
configureStore
function for each React app instance.为了为每个应用实例创建一个新的 Redux 存储实例,我们为每个 React 应用实例调用
configureStore
函数。 Our slices look similiar to this:我们的切片看起来与此类似:
import { createSlice } from '@reduxjs/toolkit'
import type { RootState } from '../../app/store'
// Define a type for the slice state
interface CounterState {
value: number
}
// Define the initial state using that type
const initialState: CounterState = {
value: 0,
}
const counterSlice = createSlice({
name: 'counter',
// `createSlice` will infer the state type from the `initialState` argument
initialState,
reducers: {
increment: (state) => {
state.value += 1
},
decrement: (state) => {
state.value -= 1
}
},
});
export const increment = (): AppThunk => async (
dispatch: AppDispatch
) => {
dispatch(indicatorsOrTopicsSlice.actions.increment());
};
export const decrement = (): AppThunk => async (
dispatch: AppDispatch
) => {
dispatch(indicatorsOrTopicsSlice.actions.decrement());
};
// Other code such as selectors can use the imported `RootState` type
export const selectCount = (state: RootState) => state.counter.value
export default counterSlice.reducer
Please note, that currently we create and export each slice statically and only once.请注意,目前我们静态地创建和导出每个切片,并且只导出一次。 Here comes my first question: Is this actually valid when creating multiple store instances or do we actually need to create also new slice instances for each app/store instance?
这是我的第一个问题:这在创建多个商店实例时是否真的有效,还是我们实际上还需要为每个应用程序/商店实例创建新的切片实例? For the simple counter example provided, doing not so, seems to work, but as soon as we use an
AsyncThunk
as in the example below the whole thing breaks.对于提供的简单计数器示例,不这样做似乎可以工作,但是一旦我们像下面的示例中那样使用
AsyncThunk
,整个事情就会中断。
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { userAPI } from './userAPI'
// First, create the thunk
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
}
)
// Then, handle actions in your reducers:
const usersSlice = createSlice({
name: 'users',
initialState: { entities: [], isLoading: false, hasErrors: false },
reducers: {
// standard reducer logic, with auto-generated action types per reducer
},
extraReducers: (builder) => {
builder.addCase(fetchUserById.pending, (state, action) => {
state.isLoading = true;
});
builder.addCase(fetchUserById.rejected, (state, action) => {
state.isLoading = false;
state.hasErrors = true;
});
builder.addCase(fetchUserById.fulfilled, (state, action) => {
// Add user to the state array
state.entities.push(action.payload);
state.isLoading = false;
state.hasErrors = true;
});
},
});
I believe the breaking starts here because of interdifferences between the events fired from dispatching the AsyncThunk
.我相信中断从这里开始是因为从调度
AsyncThunk
触发的事件之间的AsyncThunk
。 Thereby I think the solution is to call the createAsyncThunk
function for each app/store/slice instance.因此我认为解决方案是为每个应用程序/商店/切片实例调用
createAsyncThunk
函数。 Are there any best practices for doing so?有没有这样做的最佳实践? Of course this breaks the beauty and functionality of static exports and requires kind of a mapping, hence I'm asking.
当然,这破坏了静态导出的美感和功能,并且需要某种映射,因此我在问。
My original suspicion that the AsyncThunk
-part was responsible for the interferences between the stores of the different React app instances was wrong.我最初怀疑
AsyncThunk
部分是造成不同 React 应用程序实例的存储之间的干扰的原因是错误的。 The source was something different not visible in the examples provided in my question.来源与我的问题中提供的示例中不可见的不同。 We use memoized selectors via
createSelector
from reselect
.我们通过
reselect
createSelector
使用记忆化选择器。 Those were created and exported like the rest statically which in fact is a problem when working with multiple store/app instances.这些是静态创建和导出的,这实际上在使用多个商店/应用程序实例时是一个问题。 This way all instances use the same memoized selector which again doesn't work correctly thereby, since in the worst scenario the stored values of the dependency selectors are coming from the use from another store/app instance.
这样,所有实例都使用相同的记忆选择器,这再次无法正常工作,因为在最坏的情况下,依赖选择器的存储值来自另一个商店/应用程序实例的使用。 This again can lead to endless rerenderings and recomputations.
这又会导致无休止的重新渲染和重新计算。
The solution I came up with, is to create the memoized selectors for each app instance freshly.我想出的解决方案是为每个应用程序实例新鲜地创建记忆化的选择器。 Therefore I generate a unique id for each app instance which is stored permanently in the related Redux store.
因此,我为每个应用程序实例生成一个唯一的 ID,该 ID 永久存储在相关的 Redux 商店中。 When creating the store for an app instance I create also new memoized selectors instances and store them in a object which is stored in a static dictionary using the appId as the key.
在为应用程序实例创建商店时,我还创建了新的记忆选择器实例,并将它们存储在一个对象中,该对象存储在使用 appId 作为键的静态字典中。 To use the memoized selectors in our components I wrote a hook which uses
React.memo
:为了在我们的组件中使用记忆化选择器,我编写了一个使用
React.memo
的钩子:
import { useMemo } from "react";
import { useSelector } from "react-redux";
import { selectAppId } from "../redux/appIdSlice";
import { getMemoizedSelectors } from "../redux/memoizedSelectors";
// Hook for using created memoized selectors
export const useMemoizedSelectors = () => {
const appId = useSelector(selectAppId);
const allMemoizedSelectors = useMemo(() => {
return getMemoizedSelectors(appId);
}, [appId]);
return allMemoizedSelectors;
};
Then the selectors can be used in the components like this:然后可以在组件中使用选择器,如下所示:
function MyComponent(): ReactElement {
const {
selectOpenTodos,
} = useMemoizedSelectors().todos;
const openTodos = useSelector(selectOpenTodos);
// ...
}
and the related dictionary and lookup process would look like this:相关的字典和查找过程如下所示:
import { createTodosMemoizedSelectors } from "./todosSlice";
/**
* We must create and store memoized selectors for each app instance on its own,
* else they will not work correctly, because memoized value would be used for all instances.
* This dictionary holds for each appId (the key) the related created memoized selectors.
*/
const memoizedSelectors: {
[key: string]: ReturnType<typeof createMemoizedSelectors>;
} = {};
/**
* Calls createMemoizedSelectors for all slices providing
* memoizedSelectors and stores resulting selectors
* structured by slice-name in an object.
* @returns object with freshly created memoized selectors of all slices (providing such selectors)
*/
const createMemoizedSelectors = () => ({
todos: createTodosMemoizedSelectors(),
});
/**
* Creates fresh memoized selectors for given appId.
* @param appId the id of the app the memoized selectors shall be created for
*/
export const initMemoizedSelectors = (appId: string) => {
if (memoizedSelectors[appId]) {
console.warn(
`Created already memoized selectors for given appId: ${appId}`
);
return;
}
memoizedSelectors[appId] = createMemoizedSelectors();
};
/**
* Returns created memoized selectors for given appId.
*/
export const getMemoizedSelectors = (appId: string) => {
return memoizedSelectors[appId];
};
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.