如何使用redux-saga在getInitialProps中获取数据。然后在使用getInitialProps方法时从redux存储获取响应?

时间:2020-02-19 22:58:17

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

我完成了对使用create-react-app(CSR)创建的React应用的编码,但是现在我要使用Next.js框架重写整个应用,以提高SEO性能。

在重写它的过程中,我遇到了一些困难,想出如何正确处理redux和redux-saga来进行数据获取和存储的过程。 在此项目中使用Next.js的主要原因是利用getInitialProps方法,以便在第一页加载发生之前在服务器端获取数据。但是由于某些原因,我无法“等待” Redux派遣完成并按时获取提取的数据。

所以最终发生的事情是,我分派了动作来获取数据,但是在初始服务器端页面加载期间,它并没有按时存储在redux存储中。但是,当我使用next / link更改路由时,数据才进来,但只有在服务器端渲染发生后才在客户端进来。

因此有点违反了使用Next.js的目的。

此新代码与create-react-app项目非常相似,但进行了一些小的更改以适应Next.js项目的要求。

这是我的代码。

./ pages / _app.js

import App from 'next/app';
import { Provider } from 'react-redux';
import withRedux from 'next-redux-wrapper';
import withReduxSaga from 'next-redux-saga';
import makeStore from '../store/index';

class MyApp extends App {
  static async getInitialProps({ Component, ctx }) {
    const pageProps = Component.getInitialProps
      ? await Component.getInitialProps(ctx)
      : {};
    return { pageProps };
  }

  render() {
    const { Component, pageProps, store } = this.props;
    return (
      <Provider store={store}>
        <Component {...pageProps} />
      </Provider>
    );
  }
}

export default withRedux(makeStore)(MyApp);

./ pages / index.jsx:

import React from 'react';
import { useRouter } from 'next/router';

import Layout from '../components/Layout';
import SubNavBarCategories from '../components/Pages/Home/SubNavBarCategory';

import * as BlogActions from '../store/actions/blog/categories';


const Blog = (props) => {
  const router = useRouter();
  const {
    blogCategories,
  } = props;


  return (
    <Layout>
      <SubNavBarCategories blogCategories={blogCategories} />
    </Layout>
  );
};


Blog.getInitialProps = async ({ isServer, store }) => {
  await store.execSagaTasks(isServer, (dispatch) => {
    dispatch(BlogActions.getRecentCategories(5));
  });

  console.log('await store:', await store.getState().blog.blogCategories);
  //output: await store: { data: [], loading: true, fetched: false, error: false }
  //expected something like this: 
  // await store: { data: ['test1', 'category', 'crypto', 'test4', 'Day trade'] loading: false, fetched: true, error: false }

  return {
    blogCategories: await store.getState().blog.blogCategories,
  };
};

export default Blog;

./ store / index.js

import {
  createStore,
  applyMiddleware,
  compose,
} from 'redux';
import createSagaMiddleware, { END } from 'redux-saga';

import rootReducer from './reducers';
import rootSaga from './sagas';

const sagaMiddleware = createSagaMiddleware();
const makeStore = (initialState) => {
  const composeEnhancers = (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;

  const store = createStore(
    rootReducer,
    initialState,
    compose(
      composeEnhancers(applyMiddleware(sagaMiddleware)),
    ),
  );

  store.runSaga = () => {
    if (store.saga) {
      return;
    }
    store.sagaTask = sagaMiddleware.run(rootSaga);
  };


  store.stopSaga = async () => {
    if (!store.saga) {
      return;
    }
    store.dispatch(END);
    await store.saga.done;
    store.saga = null;
  };

  store.execSagaTasks = async (isServer, tasks) => {
    store.runSaga();
    tasks(store.dispatch);

    await store.stopSaga();

    if (!isServer) {
      store.runSaga();
    }
  };

  store.runSaga();
  return store;
};

export default makeStore;

./ store / actions / blog / blog.js

export function getRecentCategories(number) {
  return {
    type: 'REQUEST_RECENT_CATEGORIES',
    payload: {
      number,
    },
  };
}

./ store / reducers / blog / blog.js

import update from 'immutability-helper';

const initialState = {
  blogCategories: {
    data: [],
    loading: false,
    fetched: false,
    error: false,
  },
};

export default function blog(state = initialState, action) {
  switch (action.type) {
    case 'REQUEST_RECENT_CATEGORIES':
      return update(state, {
        blogCategories: {
          loading: { $set: true },
        },
      });
    case 'SUCCESS_RECENT_CATEGORIES':
      console.log('actions:', action.payload.data);
      //output: actions: blogCategories [ 'test1', 'category', 'crypto', 'test4', 'Day trade' ]
      return update(state, {
        blogCategories: {
          data: { $set: action.payload.data },
          loading: { $set: false },
          fetched: { $set: true },
          error: { $set: false },
        },
      });
    case 'FAILURE_RECENT_CATEGORIES':
      return update(state, {
        blogCategories: {
          fetched: { $set: true },
          error: { $set: true },
        },
      });
    default:
      return state;
  }
}

./ store / sagas / blog / getRecentCategories.js

import {
  put,
  call,
} from 'redux-saga/effects';

import 'isomorphic-fetch';

async function getRecentCategoriesApi(number) {
  const res = await fetch(`http://localhost:5000/blog/get/categories/newest/${number}`, {
    method: 'GET',
    mode: 'cors',
    cache: 'no-cache',
    credentials: 'same-origin',
    headers: {
      'Content-Type': 'application/json',
    },
  });
  const data = await res.json();
  return data;
}

export default function* asyncGetRecentCategoriesApi(action) {
  try {
    const response = yield call(getRecentCategoriesApi, action.payload.number);

    yield put({ type: 'SUCCESS_RECENT_CATEGORIES', payload: { data: response } });
  } catch (err) {
    yield put({ type: 'FAILURE_RECENT_CATEGORIES' });
  }
}

如您所见,该应用程序是一个非常普通的react redux-saga应用程序。除了使用redux-saga东西从后端获取数据之外,其他所有东西都应该正常工作。

有什么方法可以使getInitialProps方法按预期与redux和redux-saga一起使用?

1 个答案:

答案 0 :(得分:2)

看看带有redux-saga示例的Next.js官方示例。

https://github.com/zeit/next.js/tree/canary/examples/with-redux-saga