【问题标题】:How to properly hydrate Redux state in NextJS on page refresh?如何在页面刷新时正确水合 NextJS 中的 Redux 状态?
【发布时间】:2021-06-23 17:03:08
【问题描述】:

我遇到的问题是在应用重新加载/页面刷新时从本地存储中补充用户数据。

在我的项目中,我将 NextJS 用于前端,而对于支持库,我使用 redux-toolkit 进行跨应用程序的 redux 管理,next-redux-wrapper 用于包装页面的状态水合。

用户可以登录,在这种情况下,我将 isLoggedIn 布尔值存储在本地存储中并处于 redux 状态。根据isLoggedIn 布尔值,我更改Navbar 组件样式(Navbar 直接包含在_app.tsx 中)。

当用户刷新任何页面时,isLoggedIn 布尔值不会加载到状态中,而是存在于本地存储中。

过去我一直在使用 redux-persist,但我选择不使用它,因为 PersistGate 阻止 UI 呈现,直到从存储中获取持久数据,这与 SSR 的想法相冲突。

目前,我通过在_app.ts 中使用App.getInitialProps 方法修复了isLoggedIn 加载问题,然后导致为每个加载的页面调用来自next-redux-persist 的水合,但这引入了另一个问题:所有页面现在是服务器端渲染,没有 NextJS 的静态页面优化。

有什么方法可以不丢失来自 NextJS 的静态页面优化,不使用 redux-persist 库,并且在刷新任何页面时仍然能够为客户端存储补充水分?

当前代码结构(为简单起见省略了一些代码):

file: _app.tsx
import { wrapper } from 'store';

const MyApp = ({ Component, pageProps }: AppProps) => {
  return (
    <>
      <Navbar />
      <Component {...pageProps} />
    </>
  );
};


MyApp.getInitialProps = async (appContext) => {
  const appProps = await App.getInitialProps(appContext);

  return { ...appProps };
};

export default wrapper.withRedux(MyApp);
file: store.ts
import {
  combineReducers,
  configureStore,
  EnhancedStore,
  getDefaultMiddleware
} from '@reduxjs/toolkit';
import { createWrapper, MakeStore } from 'next-redux-wrapper';
import userReducer from 'lib/slices/userSlice';

const rootReducer = combineReducers({
  user: userReducer
});

const setupStore = (context): EnhancedStore => {
  const middleware = [...getDefaultMiddleware(), thunkMiddleware];

  if (process.env.NODE_ENV === 'development') {
    middleware.push(logger);
  }

  return configureStore({
    reducer: rootReducer,
    middleware,
    // preloadedState,
    devTools: process.env.NODE_ENV === 'development'
  });
};

const makeStore: MakeStore = (context) => setupStore(context);
export const wrapper = createWrapper(makeStore, {
  debug: process.env.NODE_ENV === 'development'
});
file: userSlice.ts
import { createSlice } from '@reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';

const initialState = {
  isLoggedIn: false
}

export const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    login: (state) => {
      state.isLoggedIn = true;
      localStorage.setItem('loggedInData', { isLoggedIn: true });
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(HYDRATE, (state, action: any) => {
        if (typeof window !== 'undefined') {
          const storedLoggedInData = localStorage.getItem('loggedInData');
          if (storedLoggedInData != null && storedLoggedInData) {
            const parsedJson = JSON.parse(storedLoggedInData);
            state.isLoggedIn = parsedJson.isLoggedIn ?? false;
          } else {
            state.isLoggedIn = false
          }
        }
      });
  }
});

export const isLoggedInSelector = (state: RootState) => state.user.isLoggedIn;

export default userSlice.reducer;
file: Navbar.tsx
import { useSelector } from 'react-redux';
import { isLoggedInSelector } from 'lib/slices/userSlice';

export default function Navbar() {
   const isLoggedIn = useSelector(isLoggedInSelector);
   return (
    <div className={`${ isLoggedIn ? 'logged-in-style' : 'logged-out-style'}`}>...</div>
   )
}

【问题讨论】:

    标签: typescript react-redux next.js redux-toolkit


    【解决方案1】:

    今天遇到了同样的问题。问题是我们需要将客户端存储与页面渲染解耦,并将其移动到安装组件的useEffect。基本思想是,首先完全渲染页面,然后使用客户端存储信息更新页面。

    直接合并客户端本地存储可能/将干扰水合作用。

    这是我使用的代码示例

    export default const MenuBar = () => {
      const isLoggedIn = useSelector((state) => state.isLoggedIn);
      useEffect(() => {
        // loads from clients local storage
        const auth = loadAuthenticationToken(); 
        if (auth !== null) {
          // updates the store with local storage data
          dispatch(actions.jwtTokenUpdate(auth)); 
        }
      }, [dispatch]);
      
      if (isLoggedIn) {
        return <p>you are logged in</p>;
      } else {
        return <p>please log in</p>;
      }
    }
    

    作为参考,NextJS 上的一个 github 问题:https://github.com/vercel/next.js/discussions/17443

    还有一篇博客文章需要访问窗口进行渲染:https://dev.to/adrien/creating-a-custom-react-hook-to-get-the-window-s-dimensions-in-next-js-135k

    【讨论】:

      【解决方案2】:

      文档中提到如果在 _app.js 中使用 getInitialProps 会失去静态优化。我不知道你为什么在服务器端使用 redux,我个人建议你只在客户端使用它,你不再需要使用 next-redux-wrapper,因为它在后台使用 getInitialProps。

      redux-toolkit的例子

      【讨论】:

      • 我已经阅读了有关App.getInitialProps 的文档,但我不确定在这种情况下如何避免它。对于某些页面,我使用getServerSideProps 为 SSR 获取数据。我正在使用 redux-thunk 进行 API 调用,它们将响应直接路由到 redux。我不想跳过 redux 作为单一的事实来源。这可能是一个糟糕的推理吗?
      • 当应用重新加载时,我们将如何从本地存储恢复 redux 状态?
      • 如果你有一个布局作为包装,你可以在布局中使用useEffect
      • 我建议您将令牌存储在cookies 而不是localStorage
      【解决方案3】:

      另外请注意,next-redux-wrapper 在使用 store 包装 app.js/ts 时会使用 getInitialProps。所以当你使用这个库并用它包装你的应用程序时,你会自动失去静态优化。

      【讨论】:

        猜你喜欢
        • 2020-09-07
        • 2018-07-25
        • 2019-02-09
        • 2020-09-21
        • 2019-01-20
        • 2016-08-21
        • 1970-01-01
        • 2018-08-26
        • 2017-08-19
        相关资源
        最近更新 更多