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

Posted

技术标签:

【中文标题】如何使用 redux-saga 在 getInitialProps 中获取数据。然后在 getInitialProps 方法中从 redux store 获取响应?【英文标题】:How to fetch data in getInitialProps with redux-saga.Then get the response from redux store while in the getInitialProps method? 【发布时间】:2020-06-04 05:24:49 【问题描述】:

我完成了使用 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】:

查看官方 Next.js 示例和 redux-saga 示例。

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

【讨论】:

以上是关于如何使用 redux-saga 在 getInitialProps 中获取数据。然后在 getInitialProps 方法中从 redux store 获取响应?的主要内容,如果未能解决你的问题,请参考以下文章

无法弄清楚如何使用 redux-saga-test-plan 测试 redux-saga 功能

如何使用 Redux-Saga 和 Hooks 调用 API

如何设置 redux-sagas 和文件结构?

如何将 redux-sagas 与 react-hooks 一起使用

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

多个 redux-sagas