如何监听全局动作并直接更新 rootState,同时使用 Redux Toolkit 的多个切片?
How to listen for global action and update the rootState directly, while using multiple slices with Redux Toolkit?
我的 Redux store 中有这样一个状态:
type RootState = {
PAGE_A: StatePageA,
PAGE_B: StatePageB,
PAGE_C: StatePageC,
// AND SO ON...
}
每一页都是使用 createSlice
从 @reduxjs/toolkit
创建的切片
我正在使用 Next.js 静态生成和 SSR,因此我需要使用来自 Next.js 服务器的后续页面的新状态来补充我之前创建的商店。
无论我路由到哪个页面,我都会得到预呈现的 pageState
,其类型应该是 StatePageA | StatePageB | StatePageC
.
当新状态出现时,我需要 dispatch()
一个 HYDRATE_PAGE
动作,它应该替换当前页面状态。
这是我关于如何处理 HYDRATE_PAGE
动作的第一个想法。
export const HYDRATE_PAGE = createAction<{
pageName: "PAGE_A" | "PAGE_B" | "PAGE_C",
pageState: StatePageA | StatePageB | StatePageC
}>("HYDRATE_PAGE");
然后我必须在每个切片中听它:
// EX: PAGE_A SLICE
// THIS WOULD HAVE TO BE DONE FOR EACH SLICE ON THE extraReducers PROPERTY
extraReducers: {
[HYDRATE_PAGE]: (state, action) => {
if (action.payload.pageName === "PAGE_A")
return action.payload.pageState
}
}
有没有一种方法可以集中处理它,而不必在每个 slice reducer 中监听该操作?
例如:一种 rootReducer
的东西,我可以在其中收听 HYDRATE_PAGE
动作并在一个地方处理它,例如。
// THIS REDUCER WOULD HAVE ACCESS TO THE rootState
(state: RootState, action) => {
const {pageName, pageState} = action.payload;
state[pageName] = pageState;
};
这有可能吗?
额外
有一个名为 next-redux-wrapper 的软件包,但我不打算使用它。我正在尝试构建我的自定义解决方案。
概念概述
我不确定您的商店是否拥有最佳设计,因为通常您希望按数据的类型而不是在哪个页面上使用数据来组织数据。但我只想回答这里提出的问题。
reducer 只是一个函数,它接受一个状态和一个动作,returns 是下一个状态。当您使用 combineReducers
(或 configureStore
,在内部使用 combineReducers
)组合切片缩减器的键控对象时,您会得到一个如下所示的函数:
(state: RootState | undefined, action: AnyAction) => RootState
我们想围绕该函数创建一个包装器,并添加一个额外的步骤来执行 HYDRATE_PAGE
操作。
键入操作
export const HYDRATE_PAGE = createAction<{
pageName: "PAGE_A" | "PAGE_B" | "PAGE_C",
pageState: StatePageA | StatePageB | StatePageC
}>("HYDRATE_PAGE");
您在 action creator 中的打字稿类型没有达到应有的水平,因为我们想要强制 pageName
和 pageState
相互匹配。我们真正想要的类型是这样的:
type HydratePayload = {
[K in keyof RootState]: {
pageName: K;
pageState: RootState[K];
}
}[keyof RootState];
export const HYDRATE_PAGE = createAction<HydratePayload>("HYDRATE_PAGE");
其计算结果为有效配对的并集:
type HydratePayload = {
pageName: "PAGE_A";
pageState: StatePageA;
} | {
pageName: "PAGE_B";
pageState: StatePageB;
} | {
pageName: "PAGE_C";
pageState: StatePageC;
}
修改 Reducer
首先,我们将使用 combineReducers
创建正常的 rootReducer
。然后我们创建 hydratedReducer
作为它的包装器。我们调用 rootReducer(state, action)
来获取下一个状态。大多数时候我们只是 return 那个状态并且不对它做任何事情。
但是,如果 action
匹配我们的 HYDRATE_PAGE
操作,那么我们通过用我们的有效负载中的状态替换该切片的状态来修改状态。
const rootReducer = combineReducers({
PAGE_A: pageAReducer,
PAGE_B: pageBReducer,
PAGE_C: pageCReducer
});
const hydratedReducer = (state: RootState | undefined, action: AnyAction): RootState => {
// call the reducer normally
const nextState = rootReducer(state, action);
// modify the next state when the action is hydrate
if (HYDRATE_PAGE.match(action)) {
const { pageName, pageState } = action.payload;
return {
...nextState,
[pageName]: pageState
};
}
// otherwise don't do anything
return nextState;
}
const store = configureStore({
reducer: hydratedReducer,
});
我的 Redux store 中有这样一个状态:
type RootState = {
PAGE_A: StatePageA,
PAGE_B: StatePageB,
PAGE_C: StatePageC,
// AND SO ON...
}
每一页都是使用 createSlice
从 @reduxjs/toolkit
我正在使用 Next.js 静态生成和 SSR,因此我需要使用来自 Next.js 服务器的后续页面的新状态来补充我之前创建的商店。
无论我路由到哪个页面,我都会得到预呈现的 pageState
,其类型应该是 StatePageA | StatePageB | StatePageC
.
当新状态出现时,我需要 dispatch()
一个 HYDRATE_PAGE
动作,它应该替换当前页面状态。
这是我关于如何处理 HYDRATE_PAGE
动作的第一个想法。
export const HYDRATE_PAGE = createAction<{
pageName: "PAGE_A" | "PAGE_B" | "PAGE_C",
pageState: StatePageA | StatePageB | StatePageC
}>("HYDRATE_PAGE");
然后我必须在每个切片中听它:
// EX: PAGE_A SLICE
// THIS WOULD HAVE TO BE DONE FOR EACH SLICE ON THE extraReducers PROPERTY
extraReducers: {
[HYDRATE_PAGE]: (state, action) => {
if (action.payload.pageName === "PAGE_A")
return action.payload.pageState
}
}
有没有一种方法可以集中处理它,而不必在每个 slice reducer 中监听该操作?
例如:一种 rootReducer
的东西,我可以在其中收听 HYDRATE_PAGE
动作并在一个地方处理它,例如。
// THIS REDUCER WOULD HAVE ACCESS TO THE rootState
(state: RootState, action) => {
const {pageName, pageState} = action.payload;
state[pageName] = pageState;
};
这有可能吗?
额外
有一个名为 next-redux-wrapper 的软件包,但我不打算使用它。我正在尝试构建我的自定义解决方案。
概念概述
我不确定您的商店是否拥有最佳设计,因为通常您希望按数据的类型而不是在哪个页面上使用数据来组织数据。但我只想回答这里提出的问题。
reducer 只是一个函数,它接受一个状态和一个动作,returns 是下一个状态。当您使用 combineReducers
(或 configureStore
,在内部使用 combineReducers
)组合切片缩减器的键控对象时,您会得到一个如下所示的函数:
(state: RootState | undefined, action: AnyAction) => RootState
我们想围绕该函数创建一个包装器,并添加一个额外的步骤来执行 HYDRATE_PAGE
操作。
键入操作
export const HYDRATE_PAGE = createAction<{
pageName: "PAGE_A" | "PAGE_B" | "PAGE_C",
pageState: StatePageA | StatePageB | StatePageC
}>("HYDRATE_PAGE");
您在 action creator 中的打字稿类型没有达到应有的水平,因为我们想要强制 pageName
和 pageState
相互匹配。我们真正想要的类型是这样的:
type HydratePayload = {
[K in keyof RootState]: {
pageName: K;
pageState: RootState[K];
}
}[keyof RootState];
export const HYDRATE_PAGE = createAction<HydratePayload>("HYDRATE_PAGE");
其计算结果为有效配对的并集:
type HydratePayload = {
pageName: "PAGE_A";
pageState: StatePageA;
} | {
pageName: "PAGE_B";
pageState: StatePageB;
} | {
pageName: "PAGE_C";
pageState: StatePageC;
}
修改 Reducer
首先,我们将使用 combineReducers
创建正常的 rootReducer
。然后我们创建 hydratedReducer
作为它的包装器。我们调用 rootReducer(state, action)
来获取下一个状态。大多数时候我们只是 return 那个状态并且不对它做任何事情。
但是,如果 action
匹配我们的 HYDRATE_PAGE
操作,那么我们通过用我们的有效负载中的状态替换该切片的状态来修改状态。
const rootReducer = combineReducers({
PAGE_A: pageAReducer,
PAGE_B: pageBReducer,
PAGE_C: pageCReducer
});
const hydratedReducer = (state: RootState | undefined, action: AnyAction): RootState => {
// call the reducer normally
const nextState = rootReducer(state, action);
// modify the next state when the action is hydrate
if (HYDRATE_PAGE.match(action)) {
const { pageName, pageState } = action.payload;
return {
...nextState,
[pageName]: pageState
};
}
// otherwise don't do anything
return nextState;
}
const store = configureStore({
reducer: hydratedReducer,
});