我们如何从 Redux - saga 中的 store.dispatch 返回一个 Promise,以便我们可以等待解析,然后在 SSR 中呈现?

Rah*_*ngh 5 javascript reactjs redux redux-saga

我正在尝试使用 Redux 和 Redux-saga 的 React SSR。

我能够让客户端渲染工作,但服务器存储似乎永远不会在渲染 HTML 之前获取数据/等待数据。

服务器.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');
})
Run Code Online (Sandbox Code Playgroud)

小传奇

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()
    ])
}
Run Code Online (Sandbox Code Playgroud)

错误

类型错误:无法读取 /Users/rahsinwb/Documents/Mine/code/SSR/build/bundle.js:309:39 处未定义的属性“then”

我在这里缺少什么?或者有什么行之有效的方法可以收听传奇结局?我在想我的 loadData 组件中的store.dispatch用于初始操作调用的帮助程序将返回承诺,但这永远不会起作用。

加载数据

const loadData = (store) => {
    store.dispatch({type: FETCH_USERS});
}
Run Code Online (Sandbox Code Playgroud)

更新

将此添加到我的 index.js 文件中

 store.runSaga(rootSaga).toPromise().then(() => {
        branch.map(({ route }) => {
            return route.loadData ? route.loadData(store) : Promise.resolve(null);
        });
        res.send(renderer(req, store))
    })
Run Code Online (Sandbox Code Playgroud)

现在代码编译没有任何错误,但问题是 saga 从 loadData 方法(即 FetchData)调度一个动作,在 fetchData 调用上面提到的 saga 中的 Save_DATA 动作之前,调用被取消了我的假设,并且减速器留空,这导致html 不加载数据。

我们如何知道生成器函数从发起的动作开始到加载数据的动作完成了数据加载?到减速器作为我应该在服务器上呈现组件的唯一帖子

更新2

还检查了一个lib,但由于某种奇怪的原因,它引发了再生器问题,但我不认为一个 lib 需要用于这样的目的,因为我计划为客户端 Redux 调用重用相同的 saga 并希望将更改分开。索引.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))
   )
Run Code Online (Sandbox Code Playgroud)

添加设置超时有帮助,我能够在服务器中获取数据然后渲染,所以现在问题的关键是我们需要一些如何等待生成器函数解决然后调用渲染。

更新 3

添加了解决此问题的方法作为答案,但我不认为这是最好的解决方案,可以将此逻辑抽象出来

ooz*_*ers 3

类型错误:无法读取未定义的属性“then”

.donegetter 自 v1 起已被弃用,.toPromise()监听 saga 解析的方法也是如此

至于等待传奇完成,END您可以调度一个特殊的操作来在传奇的所有子任务完成时终止传奇,从而导致store.runSaga(rootSaga).toPromise()承诺解决。

当承诺得到解决(意味着所有必需的数据已经存在)时,您应该发送从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);
});

Run Code Online (Sandbox Code Playgroud)

另外,您也可以按照此示例进行操作。