我完成了一个使用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一起工作?
看看带有redux-saga示例的官方next.js示例。
https://github.com/zeit/next.js/tree/canary/examples/with-redux-saga
问题内容: 如何在saga函数中访问redux状态? 简短答案: 问题答案: 正如@markerikson已经说过的那样,它公开了一个非常有用的API 来调用状态,以便在传奇中获取部分状态。 对于您的示例,一个简单的实现可以是: 除了@markerikson 建议的文档之外,D。Abramov还提供了一个非常好的视频教程,其中介绍了如何与Redux 一起使用。在Twitter上也请检查这个有趣的线
问题内容: 我有些困惑,很想得到一个答案,可以帮助我理清思路。假设我有一个后端(nodejs,express等),我在其中存储用户及其数据,有时我想从后端获取数据,例如用户登录后的用户信息或产品列表并保存他们在状态。 到目前为止,我所看到的方法是,在组件加载之前获取数据,并使用响应中的数据调度操作。但是我最近开始对此进行深入研究,并且看到了我较早知道的React- Thunk库,并开始怀疑从后端/
问题内容: 我是reactjs / redux的初学者,找不到如何使用api调用在redux应用程序中检索数据的简单示例。我猜您可以使用jquery ajax调用,但是那里可能还有更好的选择? 问题答案: JSfiddle; http://jsfiddle.net/cdagli/b2uq8704/6/ 它使用redux,redux-thunk和fetch。 提取方法; 上面使用的动作: (注意:您
问题内容: 如何从Redux表单传递数据,以便可以在App.js中访问该数据?这是我使用redux- form编写的代码。单击提交后,数据应该从form.js传递到app.js. 并且数据应显示在页面上。 这是form.js- 如何将输入的数据传递给app.js? 问题答案: 您需要做的就是获取值 因此,在App.js中,您可以拥有
问题内容: 场景是,我要重定向用户或根据成功显示警报,在分派操作后显示错误回调。 下面是使用 redux-thunk 完成任务的代码 因为redux-thunk中的调度动作返回了Promise,所以响应起来很容易。 但是现在,我开始沉迷于redux- saga,试图弄清楚如何获得与上述代码相同的结果。由于Saga在不同的线程上运行,因此无法从上面的查询中获取回调。所以我只想知道你们是如何做到的。还
但我正在寻找一种更优雅的方式来存储存储中的获取状态。 因此,我尝试了另一种方法,并考虑创建一个fetchingActionsReducer,它将每个提取操作添加到存储中的dict(对象),然后状态如下所示: 导出常量loadingTasks=(state={},action)=>{switch(action.type){case start_loading_task:return object.a