我们如何在 Redux - saga 中 return 来自 store.dispatch 的 Promise,以便我们可以等待 resolve 然后在 SSR 中渲染?
How do we return a Promise from a store.dispatch in Redux - saga so that we can wait for the resolve and then render in SSR?
我正在尝试使用 Redux 和 Redux-saga 进行 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: Cannot read property 'then' of undefined
at /Users/rahsinwb/Documents/Mine/code/SSR/build/bundle.js:309:39
我在这里错过了什么?或者有什么行之有效的方法可以让我们听到传奇的结局吗?我在想我的 loadData 助手在 store.dispatch
的组件中用于初始操作调用将 return 承诺,但这永远行不通。
加载数据
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 动作之前,调用被取消我的假设,并且减速器留空,导致 html 无法加载数据。
How do we know that the data load is completed for the generator
function starting from the action initiated to the action for loading data ?
to the reducer as only post that i should render component on the server
更新2
还检查了一个 lib,但出于某种奇怪的原因,它引发了再生器问题,但我认为不需要库来实现这样的目的,因为我计划为客户端 Redux 调用重用相同的 saga to 和 want to keep changes 分开了。
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))
)
Adding Set Timeout helps and i am able to fetch data in server and
then render so now the CRUX of the problem is that we need to some how
wait for the generator function to resolve then call render.
更新 3
添加了解决此问题的方法作为答案,但我认为这不是最好的解决方案并且可以将此逻辑抽象出来
我能够找到一种方法来解决这个问题,但这不是一个干净的解决方案,我不想对每个 loadData 方法 + saga 都这样做。我能够抽象它是否有更清晰的方法来实现这一点,因为我仍然对答案不满意
在组件中加载数据
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);
}
现在我可以像 Post
中提到的那样使用 Promise.All
Promise.all(promises).then(data => {
console.log('here',data);
res.send(renderer(req, store))
})
Is there a cleaner way for this problem as i need to do it for other
saga's too .
TypeError: Cannot read property 'then' of undefined at
.done
getter 自 v1 以来已弃用,因此 .toPromise()
是监听 saga resolve
的方式
至于等待 sagas 完成,有一个特殊的 END
动作,当它的所有子任务完成时,您可以调度它来终止 saga,导致 store.runSaga(rootSaga).toPromise()
承诺解决。
当 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);
});
另外,您可以关注this example。
我正在尝试使用 Redux 和 Redux-saga 进行 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: Cannot read property 'then' of undefined at /Users/rahsinwb/Documents/Mine/code/SSR/build/bundle.js:309:39
我在这里错过了什么?或者有什么行之有效的方法可以让我们听到传奇的结局吗?我在想我的 loadData 助手在 store.dispatch
的组件中用于初始操作调用将 return 承诺,但这永远行不通。
加载数据
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 动作之前,调用被取消我的假设,并且减速器留空,导致 html 无法加载数据。
How do we know that the data load is completed for the generator function starting from the action initiated to the action for loading data ? to the reducer as only post that i should render component on the server
更新2
还检查了一个 lib,但出于某种奇怪的原因,它引发了再生器问题,但我认为不需要库来实现这样的目的,因为我计划为客户端 Redux 调用重用相同的 saga to 和 want to keep changes 分开了。 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))
)
Adding Set Timeout helps and i am able to fetch data in server and then render so now the CRUX of the problem is that we need to some how wait for the generator function to resolve then call render.
更新 3
添加了解决此问题的方法作为答案,但我认为这不是最好的解决方案并且可以将此逻辑抽象出来
我能够找到一种方法来解决这个问题,但这不是一个干净的解决方案,我不想对每个 loadData 方法 + saga 都这样做。我能够抽象它是否有更清晰的方法来实现这一点,因为我仍然对答案不满意
在组件中加载数据
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);
}
现在我可以像 Post
中提到的那样使用 Promise.AllPromise.all(promises).then(data => {
console.log('here',data);
res.send(renderer(req, store))
})
Is there a cleaner way for this problem as i need to do it for other saga's too .
TypeError: Cannot read property 'then' of undefined at
.done
getter 自 v1 以来已弃用,因此 .toPromise()
是监听 saga resolve
至于等待 sagas 完成,有一个特殊的 END
动作,当它的所有子任务完成时,您可以调度它来终止 saga,导致 store.runSaga(rootSaga).toPromise()
承诺解决。
当 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);
});
另外,您可以关注this example。