【问题标题】:Is using Redux with Next.js an anti-pattern?将 Redux 与 Next.js 一起使用是一种反模式吗?
【发布时间】:2020-06-22 20:55:08
【问题描述】:

我正在构建一个 Next.js 应用程序,它目前正在使用 Redux。在构建它时,我想知道是否真的需要使用 Redux,以及它的使用是否实际上是一种反模式。这是我的推理:

为了在 Next.js 中正确初始化 Redux Store,您必须使用 getInitialProps 方法创建一个自定义的 App 组件。通过这样做,您将禁用 Next.js 提供的 Automatic Static Optimization

相比之下,如果我要在客户端包含 Redux,只有在 App 挂载后,Redux 商店才会在每次服务器端导航后重置。例如,我有一个 Next.js 应用程序,它在客户端初始化 Redux 存储,但是当路由到动态路由(例如 pages/projects/[id])时,页面是服务器端呈现的,我必须重新获取任何商店中的信息。

我的问题是:

  1. 在这种情况下,Redux 存储有什么好处?
  2. 我是否应该在根App 组件中初始化存储并放弃自动静态优化?
  3. 在 Next.js 9.3 中使用getStaticPropsother data fetching methods 管理状态是否有更好的方法?
  4. 我错过了什么吗?

【问题讨论】:

    标签: reactjs redux react-redux next.js server-side-rendering


    【解决方案1】:

    如果您使用 Redux,则不需要在 _app.js 上具有 getInitialProps。

    你可以使用 next-redux-wrapper,只需用它包装 _app.js 导出。

    使用 next-redux-wrapper 和 thunk 存储示例:

    import { createStore, applyMiddleware } from 'redux';
    import { createWrapper } from 'next-redux-wrapper';
    import { composeWithDevTools } from 'redux-devtools-extension';
    
    import thunkMiddleware from 'redux-thunk';
    import rootReducer from './rootReducer';
    
    const bindMiddleware = middleware => {
        return composeWithDevTools(applyMiddleware(...middleware));
    };
    
    const initStore = (initialState = {}) => {
        return createStore(rootReducer, initialState, bindMiddleware([thunkMiddleware]));
    };
    
    export const wrapper = createWrapper(initStore, { debug: true });
    

    然后在你的 _app.js 中,你将它作为功能组件导出

    const App = ({ Component, pageProps }) => {
       return (
          <Component {...pageProps} />
       )
    }    
    export default wrapper.withRedux(App);
    

    像魅力一样工作。只要确保你在做水合 ssr -> csr。

    【讨论】:

      【解决方案2】:

      我个人认为在任何情况下使用 Redux 都不是一个好主意。最好使用,例如,useContext,或者在极端需要集中存储的情况下使用 mobx。但实际上,有一种简单的方法可以在不使用 getInitialProps 的情况下将 Redux 与 SSR 一起使用。

      这里有一点很重要——我给出的解决方案只有在你不使用服务器上每个页面的渲染时才适用——在第一次渲染后遵循路由时,应用程序会自行渲染下一页.在这个解决方案中,假设 store 将在服务器端初始化一次,然后将渲染结果传输到客户端。如果每次导航路由时都需要在服务器上渲染页面并且需要保存 store 的状态,那么也许你最好还是看看 next-redux-wrapper。

      因此,首先要在 getServerSideProps 初始化存储,您需要按如下方式更改存储初始化文件(也许您会有其他导入):

      import { createStore, applyMiddleware } from 'redux';
      import thunkMiddleware from 'redux-thunk';
      import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';
      
      let storeInstance: any;
      export const makeStore = (initialState: {}) => {
          storeInstance = createStore(
              Reducers,
              initialState,
              composeWithDevTools(applyMiddleware(thunkMiddleware)) // Optional, but is a handy thing
          );
          return storeInstance;
      };
      
      // initializeStore used for pages that need access to store at getServerSideProps
      export const initializeStore = (preloadedState) => {
          let reInitiatedStore = storeInstance ?? makeStore(preloadedState)
      
          // After navigating to a page with an initial Redux state, merge that state
          // with the current state in the store, and create a new store
          if (preloadedState && storeInstance) {
              reInitiatedStore = makeStore({ ...storeInstance.getState(), ...preloadedState});
              // Reset the current store
              storeInstance = undefined;
          }
      
          // Keep in mind that in some cases this can cause strange
          // and difficult to track errors, so whether or not
          // to uncomment next lines depends on the architecture of your application.
          // if (typeof(window) === 'undefined') {
          //    return reInitiatedStore; // For SSG and SSR always create a new store
          // }
      
          // Create the store once in the client
          if (!storeInstance) {
              storeInstance = reInitiatedStore;
          }
      
          return reInitiatedStore;
      }
      

      之后,在页面中,您需要在getServerSideProps中存储在服务器端,您可以简单地使用initializeStore:

      import { initializeStore } from '@Redux';
      
      // Compnent code here...
      
      export const getServerSideProps(context: any) {
          const reduxStore = initializeStore();
          // reduxStore = {
          // dispatch: [Function (anonymous)],
          // subscribe: [Function: subscribe],
          // getState: [Function: getState],
          // }
      
          // Doing something with the storage...
      
          const initialReduxState = storeInstance.getState(); // and get it state
          return { props: { initialReduxState, ...someProps } };
      }
      

      另外别忘了,如果你需要在你的 _app.js 中访问 store,你必须将 store 定义为:

      const store = initializeStore(pageProps.initialReduxState);
      

      【讨论】:

        【解决方案3】:

        我们使用 Redux 主要有两个原因。

        1- 在组件之间传递数据。

        如果你不使用redux,那么你需要做prop钻孔。为了决定用户是否登录,我们获取数据,然后将其存储在 redux 存储中,然后 Header 组件连接到存储并获取身份验证信息。如果你不使用redux,那么你需要在每个页面中获取用户,然后将其传递给Header组件。

        Next.js 预渲染每个页面。这意味着 Next.js 会提前为每个页面生成 HTML,而不是全部由客户端 JavaScript 完成。预渲染可以带来更好的性能和 SEO。 next-redux-wrapper 包允许您使用带有自动静态优化的 redux。如果您单击该链接,则会有一条注释说:“Next.js 在使用类 MyApp 时提供通用 getInitialProps,这将被包装器拾取,因此您不能扩展 App,因为您将选择退出自动静态优化:”。我为我的项目设置了这个包,它很容易设置。

        但是使用 redux 的缺点是没有缓存。您存储数据,然后定期重新获取它以确保它是最新的。这是一项额外昂贵的工作。为了在 redux 中实现缓存,我们使用reselect 库。这意味着你的项目对 redux 的额外依赖,会让你写更多的代码。

        有一个很好的包swr,它是由 next.js 创建的。 Stale-While-Revalidate。它首先从缓存(stale)返回数据,然后发送获取请求,最后再次带有更新的数据。我在每个页面中选择使用这个。

        import useSWR from "swr";
        
        export const useGetUser = () => {
             // fetcher can be any asynchronous function which returns the data. useSwr will pass "/api/v1/me" to fetcher
             const { data, error, ...rest } = useSWR("/api/v1/me", fetcher);
             // !data && !error if both true, loading:true, data=null=>!data=true, error=null => !error=true
             return { data, error, loading: !data && !error, ...rest };
           };
        

        这里是可重用的提取器

        export const fetcher = (url: string) =>
          fetch(url).then(
            async (res: Response): Promise<any> => {
              const result = await res.json();
        
              if (res.status !== 200) {
                return Promise.reject(result);
              } else {
                return result;
              }
            }
          );
        

        2- 发出 api 请求。

        我为我的项目设置了 redux 存储,它与我设置的文本编辑器冲突。 Redux 以某种方式阻止了编辑器,我无法用我在编辑器上写的文本填充商店。所以我使用可重用的钩子来获取 api。一开始看起来很亲切,但如果你分析它,它就会有意义。

        export function useApiHandler(apiCall) {
          // fetching might have one those 3 states. you get error, you fetch the data, and you start with the loading state
          const [reqState, setReqState] = useState({
            error:null,
            data:null,
            loading:true, // initially we are loading 
          });
          const handler = async (...data) => {
            setReqState({ error: null, data: null, loading: true });
            try {
              // apiCall is a separate function to fetch the data
              const res = await apiCall(...data);
              setReqState({ error: null, data: res.data, loading: false });
              alert(res.data);// just to check it 
              return res.data;
            } catch (e) {
              // short circuting in or. if first expression is true, we dont evaluate the second.
              // short circuting in and. if first expression is true, result is the second expression
              const message =
                (e.response && e.response.data) || "Ooops, something went wrong...";
              setReqState({ error: message, data: null, loading: false });
              return Promise.reject(message);
            }
          };
        
          return [handler, { ...reqState }];
        }
        

        一个简单的apiCall函数

          const createBlog = (data) => axios.post("/api/v1/blogs", data);
        

        然后我们就是这样使用它的:

          export const useCreateBlog = () => useApiHandler(createBlog);
        

        设置 redux 很容易,因为人们很容易不用担心应用程序的性能,他们只需设置即可。在我看来,如果你有一个大型应用程序,你需要设置 redux,或者如果你熟悉 graphql,你可以使用 Apollo。这是一篇很好的文章,可以了解如何使用 apollo 作为状态管理。 apollo as state management。我建立了一个大型电子商务网站,并在我的新应用中使用了 redux,因为它相对较小,我不使用 next js 并使其更复杂。

        【讨论】:

          【解决方案4】:

          如果您有一个带有 getInitialProps 的自定义应用程序,那么 Automatic Next.js 提供的静态优化将对所有人禁用 页面。

          没错,如果你遵循这种方法。

          有没有更好的办法?

          是的,您可以创建一个 Redux Provider 作为包装器并包装您需要的组件,redux 上下文将自动初始化并在该组件中提供。

          例子:

          const IndexPage = () => {
            // Implementation
            const dispatch = useDispatch()
            // ...
            // ...
            return <Something />;
          }
          
          IndexPage.getInitialProps = ({ reduxStore }) => {
            // Implementation
            const { dispatch } = reduxStore;
            // ...
            // ...
          }
          
          export default withRedux(IndexPage)
          

          您现在可以将 Redux 仅用于需要状态管理的页面,而无需禁用整个 App 的优化。

          回答您的问题“将 Redux 与 Next.js 一起使用是一种反模式吗?

          不可以,但需要正确使用。

          有关如何在此处完成的更多信息:https://github.com/vercel/next.js/tree/canary/examples/with-redux

          希望对你有帮助

          【讨论】:

            猜你喜欢
            • 2020-04-08
            • 2019-02-05
            • 2021-07-14
            • 1970-01-01
            • 1970-01-01
            • 2021-09-16
            • 2018-10-04
            • 1970-01-01
            相关资源
            最近更新 更多