0

我的 Redux 商店中有一个状态,形状如下:

type RootState = {
 PAGE_A: StatePageA,
 PAGE_B: StatePageB,
 PAGE_C: StatePageC,
 // AND SO ON...
}

每个页面都是使用createSlicefrom创建的切片@reduxjs/toolkit

我正在使用 Next.js 静态生成和 SSR,因此我需要使用来自 Next.js 服务器的后续页面的新状态来补充我之前创建的商店。

无论我要路由到什么页面,我都会得到 pre-rendered pageState,它应该是 type 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
  }
}

有没有一种方法可以集中处理它,而不必在每个切片缩减器中监听该操作?

例如: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 的包,但我不打算使用它。我正在尝试构建我的自定义解决方案。

4

1 回答 1

1

概念概述

我不确定您的商店是否拥有最好的设计,因为通常您希望数据按照它的数据类型而不是它使用的页面来组织。但我只是要回答这里提出的问题。

reducer 是一个简单的函数,它接受一个状态和一个动作并返回下一个状态。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");

您在动作创建器中拥有的打字稿类型不如它们所能达到的那样好,因为我们希望强制pageNamepageState相互匹配。我们真正想要的类型是这样的:

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

修改减速器

首先,我们将创建法线rootReducercombineReducers然后我们将其创建hydratedReducer为包装器。我们调用rootReducer(state, action)以获得下一个状态。大多数时候,我们只是返回那个状态而不对它做任何事情。但是,如果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,
});
于 2021-04-16T22:24:49.090 回答