我正在尝试使用Redux和Redux传奇进行React SSR。
我能够让客户端渲染工作,但服务器存储似乎从未获得数据/在渲染HTML之前等待数据。
server.js
import express from 'express';
import renderer from "./helpers/renderer";
import createStore from "./helpers/createStore";
import {matchRoutes} from 'react-router-config';
import Routes from "./client/Routes";
import rootSaga from "./client/saga";
const app = express();
app.use(express.static('public'));
app.get('*', (req, res) => {
const store = createStore();
const branch = matchRoutes(Routes, req.path);
const promises = branch.map(({ route }) => {
return route.loadData ? route.loadData(store) : Promise.resolve(null);
});
store.runSaga(rootSaga).done.then(() => {
res.send(renderer(req, store)) // helper method to load static router and provider
}) // Not required after Update 2 , Promise.All should work ..
});
app.listen(3000, () => {
console.log('Listening on Port 3000');
})
小传奇
import { put, takeEvery, all, call } from 'redux-saga/effects'
import {FETCH_USERS, SAVE_USERS} from "../actions";
import axios from 'axios';
export function* fetchUsers() {
const res = yield call(getUsers);
yield put({ type: SAVE_USERS, payload: res.data});
}
const getUsers = () => {
const response = axios.get('http://react-ssr-api.herokuapp.com/users');
return response;
}
export function* actionWatcher() {
yield takeEvery(FETCH_USERS, fetchUsers)
}
export default function* rootSaga() {
yield all([
actionWatcher()
])
}
错误
TypeError:无法读取未定义的属性'then'在/Users/rahsiwb/Documents/Mine/code/SSR/build/bundle.js:309:39
这里缺少什么?或者,有什么行之有效的方法可以让我们倾听传奇故事的结局吗?我在想,我在组件中使用store.dispatch
进行初始操作调用的loadData助手将返回promise,但这永远不会起作用。
加载数据
const loadData = (store) => {
store.dispatch({type: FETCH_USERS});
}
更新
将此添加到我的index.js文件
store.runSaga(rootSaga).toPromise().then(() => {
branch.map(({ route }) => {
return route.loadData ? route.loadData(store) : Promise.resolve(null);
});
res.send(renderer(req, store))
})
现在代码编译没有任何错误,但问题是saga从loadData方法(即FetchData(调度了一个操作,在FetchData调用上面提到的saga中的Save_DATA操作之前,我假设调用被取消,reducer为空,这导致html没有加载数据。
我们如何知道生成器的数据加载已完成函数从启动的动作开始到加载数据的动作?作为唯一的帖子,我应该在服务器上呈现组件
Update2
我也检查了一个lib,但出于某种奇怪的原因,它引发了再生器问题,但我不认为lib是必要的,因为我计划为客户端Redux调用重用相同的saga,并希望将更改分开。index.js
console.log(promises); // no promise returned undefined
//setTimeout((function() {res.send(renderer(req, store))}), 2000);
Promise.all(promises).then(
res.send(renderer(req, store))
)
添加Set Timeout有帮助,我可以在服务器和然后渲染,所以现在CRUX的问题是,我们需要一些等待生成器函数解析,然后调用render。
更新3
添加了一种绕过这个问题的方法作为答案,但我觉得这不是最好的解决方案,可以抽象出的逻辑
TypeError:无法读取上未定义的属性'then'
.done
getter从v1开始就被弃用,因此.toPromise()
是监听传奇解析的方法
至于等待saga完成,有一个特殊的END
操作,当它的所有子任务都完成时,您可以调度它来终止saga,从而导致store.runSaga(rootSaga).toPromise()
promise得到解决。
当promise得到解决(意味着所有需要的数据都已经存在(时,您应该发送从renderToString
收到的结果HTML作为响应。请注意,服务器渲染的组件并没有真正挂载,所以componentDidMount()
方法不会被触发,因此数据不会被提取两次。
所以你可以修改你的代码如下:
// server.js
...
import { END } from 'redux-saga';
app.get('*', (req, res) => {
const store = createStore();
const branch = matchRoutes(Routes, req.path);
store.runSaga(rootSaga).toPromise().then(() => {
res.send(renderer(req, store))
});
// trigger data fetching
branch.forEach(({ route }) => {
route.loadData && route.loadData(store)
});
// terminate saga
store.dispatch(END);
});
此外,您可以按照这个示例进行操作。
我找到了一种方法来解决这个问题,但这并不是一个干净的解决方案,我不想对每个loadData方法+传奇都这样做。我能够抽象出有没有一种更干净的方法来实现这一点,因为我仍然不满意的答案
组件中的loadData
const loadData = (store) => {
//store.dispatch({type: FETCH_USERS})
return new Promise((resolve) => {
store.dispatch({type: FETCH_USERS, callback: resolve })
});
}
佐贺
function asyncCallBackUtil(action){
if (action.callback) {
action.callback('Done');
}
}
export function* fetchUsers(action) {
const res = yield call(getUsers);
yield put({ type: SAVE_USERS, payload: res.data});
asyncCallBackUtil(action);
}
现在我可以使用Promise了。正如后上提到的那样
Promise.all(promises).then(data => {
console.log('here',data);
res.send(renderer(req, store))
})
有没有一种更干净的方法来解决这个问题,因为我需要为其他问题做这件事传奇也是。