简体   繁体   English

将 Redux 与 Next.js 一起使用是一种反模式吗?

[英]Is using Redux with Next.js an anti-pattern?

I'm building a Next.js app and it currently is using Redux.我正在构建一个 Next.js 应用程序,它目前正在使用 Redux。 As I am building it I am wondering if the use of Redux is really necessary and if its use is actually an anti-pattern.在构建它时,我想知道是否真的有必要使用 Redux,以及它的使用是否实际上是一种反模式。 Here is my reasoning:这是我的推理:

In order to properly initialize the Redux Store in Next.js, you must create a custom App component with a getInitialProps method.为了在 Next.js 中正确初始化 Redux Store,您必须使用getInitialProps方法创建自定义App组件。 By doing this you are disabling the Automatic Static Optimization that Next.js provides.通过这样做,您将禁用 Next.js 提供的自动静态优化

By contrast, if I were to include Redux on the client-side, only after the App has mounted, then the Redux store will reset after every server-side navigation.相比之下,如果我要在客户端包含 Redux,只有在 App 挂载后,Redux 商店才会在每次服务器端导航后重置。 For instance, I have a Next.js app that initializes the Redux store on the client-side, but when routing to a dynamic route such as pages/projects/[id] , the page is server-side rendered, and I have to re-fetch any information that was in the store.例如,我有一个 Next.js 应用程序,它在客户端初始化 Redux 存储,但是当路由到动态路由(例如pages/projects/[id]时,页面是服务器端呈现的,我必须重新获取商店中的任何信息。

My questions are:我的问题是:

  1. What are the benefits of a Redux store in this circumstance?在这种情况下,Redux 存储有什么好处?
  2. Should I initialize the store in the root App component and forego the Automatic Static Optimization?我应该在根App组件中初始化商店并放弃自动静态优化吗?
  3. Is there a better way to do to manage state in Next.js 9.3 with getStaticProps and the other data fetching methods有没有更好的方法来使用getStaticProps其他数据获取方法来管理 Next.js 9.3 中的状态
  4. Am I missing something?我错过了什么吗?

If you have a custom App with getInitialProps then the Automatic Static Optimization that Next.js provides will be disabled for all pages.如果您有一个带有 getInitialProps 的自定义应用程序,那么 Next.js 提供的自动静态优化将为所有页面禁用。

True, if you follow this approach.没错,如果您遵循这种方法。

Is there a better way ?有没有更好的办法 ?

Yes, you can create a Redux Provider as a wrapper and wrap the component you need, the redux context will be automatically initialized and provided within that component.是的,您可以创建一个 Redux Provider 作为包装器并包装您需要的组件,redux 上下文将自动初始化并在该组件中提供。

Example:例子:

const IndexPage = () => {
  // Implementation
  const dispatch = useDispatch()
  // ...
  // ...
  return <Something />;
}

IndexPage.getInitialProps = ({ reduxStore }) => {
  // Implementation
  const { dispatch } = reduxStore;
  // ...
  // ...
}

export default withRedux(IndexPage)

You have now the possibility to use Redux only for the pages which need state management without disabling the optimization for the entire App.您现在可以仅将 Redux 用于需要状态管理的页面,而无需禁用整个 App 的优化。

Answering you question " Is using Redux with Next.js an anti-pattern? "回答您的问题“将 Redux 与 Next.js 一起使用是一种反模式吗?

No, but it needs to be used properly.不可以,但需要正确使用。

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

I hope this helps我希望这有帮助

we use Redux mainly for 2 reasons.我们使用 Redux 主要有两个原因。

1- pass data between components. 1- 在组件之间传递数据。

if you do not use redux, then you need to do prop drilling.如果你不使用redux,那么你需要做道具钻孔。 To decide if user logged in or not, we fetch the data and then store it in redux store and then Header components connects to the store and gets the authentication info.为了决定用户是否登录,我们获取数据,然后将其存储在 redux 存储中,然后 Header 组件连接到存储并获取身份验证信息。 If you are not using redux, then you need to fetch the user in each page and then pass it to the Header component.如果你不使用 redux,那么你需要在每个页面中获取用户,然后将其传递给 Header 组件。

Next.js pre-renders every page. Next.js 预渲染每个页面。 This means that Next.js generates HTML for each page in advance, instead of having it all done by client-side JavaScript.这意味着 Next.js 会提前为每个页面生成 HTML,而不是全部由客户端 JavaScript 完成。 Pre-rendering can result in better performance and SEO.预渲染可以带来更好的性能和 SEO。 next-redux-wrapper package allows you to use the redux with automatic-static-optimization. next-redux-wrapper包允许您使用带有自动静态优化的 redux。 If you click on the link, there is a note saying: "Next.js provides generic getInitialProps when using class MyApp extends App which will be picked up by wrapper, so you must not extend App as you'll be opted out of Automatic Static Optimization:".如果您单击该链接,则会有一条注释说:“Next.js 在使用类 MyApp 时提供通用 getInitialProps,这将被包装器拾取,因此您不能扩展 App,因为您将选择退出自动静态优化:”。 I set up this package for my project and it is easy to setup.我为我的项目设置了这个包,它很容易设置。

But downside of using redux, it is not caching.但是使用 redux 的缺点是它没有缓存。 You store the data and then you refetch it periodically to make sure it is up to date.您存储数据,然后定期重新获取它以确保它是最新的。 and this is an extra expensive work.这是一项额外昂贵的工作。 To achieve caching in redux, we use reselect library.为了在 redux 中实现缓存,我们使用reselect库。 This means extra dependency for your project on top of redux and will make you write more code.这意味着你的项目在 redux 之上的额外依赖,会让你编写更多的代码。

There is a nice package swr which is created by next.js.有一个不错的包swr ,它是由 next.js 创建的。 Stale-While-Revalidate. Stale-While-Revalidate。 it first returns the data from cache(stale), then sends the fetch request, and finally comes with the updated data again.它首先从缓存(stale)返回数据,然后发送获取请求,最后再次带有更新的数据。 I choose the use this in each page.我在每个页面中选择使用这个。

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

here is resuable fetcher这是可重复使用的提取器

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- Making api requests. 2- 发出 api 请求。

I set up redux store for my project and it was conflicting with the text-editor that I set up.我为我的项目设置了 redux 商店,它与我设置的文本编辑器冲突。 Redux was somehow blocking the editor and i could not populate the store with the text that i wrote on the editor. Redux 以某种方式阻止了编辑器,我无法用我在编辑器上写的文本填充商店。 So I used reusable hooks for fetching api.所以我使用可重用的钩子来获取 api。 it looks intimating in the beginning but if you analyze it, it will make sense.一开始它看起来很亲密,但如果你分析它,它就会有意义。

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

A simple apiCall function一个简单的 apiCall 函数

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

and then this is how we use it :然后这就是我们使用它的方式:

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

Setting redux is easy since it is easy people are not worried about the performance of their app, they just set it up.设置 redux 很容易,因为人们很容易不担心他们的应用程序的性能,他们只是设置它。 In my opinion, if you have a large app you need to set up redux or if you are familiar with graphql you can use Apollo.在我看来,如果你有一个大型应用程序,你需要设置 redux,或者如果你熟悉 graphql,你可以使用 Apollo。 Here is a good article to get an idea about using apollo as state management.这是一篇很好的文章,可以了解如何使用 apollo 作为状态管理。 apollo as state management . 阿波罗作为状态管理 I built a large ecommerce website and I used redux, my in my new app, since it is relatively small I do not use next js and make it more complicated.我建立了一个大型电子商务网站,并在我的新应用程序中使用了 redux,因为它相对较小,我不使用 next js 并使其更复杂。

If you are using Redux, you do not need to have getInitialProps on _app.js.如果你使用 Redux,你不需要在 _app.js 上有 getInitialProps。

You can use next-redux-wrapper, and just wrap _app.js export with it.你可以使用 next-redux-wrapper,并用它来包装 _app.js 导出。

Store example, with next-redux-wrapper and thunk:使用 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 });

Then inside your _app.js, you are exporting it as functional component with然后在您的 _app.js 中,您将其导出为功能组件

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

Works like a charm.奇迹般有效。 Just make sure you are doing hydration ssr -> csr.只要确保你在做水合 ssr -> csr。

Next.js is just a framework on top of React which simplifies Server Side Rendering setup, but it is still React. Next.js 只是 React 之上的一个框架,它简化了服务器端渲染设置,但它仍然是 React。 And React/Redux combo is very popular and still often used, also by me, so the answer is - it is not necessary, but totally possible! React/Redux 组合非常流行并且仍然经常被我使用,所以答案是 - 这不是必需的,但完全有可能! The bigger the app and the more you like functional programming, the better chance Redux will be a good option!应用程序越大,你越喜欢函数式编程,Redux 就越有可能成为一个不错的选择!

Personally I think using the Redux is not a good idea at any case.我个人认为在任何情况下使用 Redux 都不是一个好主意。 It would be better to use, for example, useContext, or in case of extreme need for centralized storage look towards mobx.最好使用例如 useContext,或者在极端需要集中存储的情况下,请使用 mobx。 But in fact, there is a simple way to use Redux with SSR without using getInitialProps.但实际上,有一种简单的方法可以在不使用 getInitialProps 的情况下将 Redux 与 SSR 一起使用。

There is an important point here - the solution I gave is applicable only if you DO NOT use the rendering of literally every page on the server - when following the route after the first render, the application renders the next page on its own.这里有一点很重要——我给出的解决方案只有在你不使用服务器上每个页面的渲染时才适用——在第一次渲染后遵循路由时,应用程序会自行渲染下一页。 In this solution it is assumed that the store will be initialized on the server side once and then the rendering result will be transferred to the client.在这个解决方案中,假设 store 将在服务器端初始化一次,然后将渲染结果传输到客户端。 If you need to render the page on the server absolutely every time you navigate the route and you need to save the state of store, then perhaps you really better still look towards the next-redux-wrapper.如果每次导航路由时都需要在服务器上渲染页面并且需要保存 store 的状态,那么也许你最好还是看看 next-redux-wrapper。

So to initialize store at getServerSideProps first you will need to change your storage initialization file as follows (perhaps you will have other imports):因此,首先要在 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;
}

After that, in the page, where you need store on server side in the getServerSideProps, you can simple use initializeStore:之后,在页面中,您需要在 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 } };
}

Also don't forget that if you need to access the store in your _app.js, you must define store as:另外不要忘记,如果您需要访问 _app.js 中的商店,您必须将商店定义为:

const store = initializeStore(pageProps.initialReduxState);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM