React与Redux和Router - 服务器端渲染

4
我正在尝试使用异步调用使服务器渲染生效。我的问题是,在渲染之前数据没有被下载。
以下是我的代码(也可在github上查看:https://github.com/tomekbuszewski/server-rendering):
server.js
require('babel-register');

import express from 'express';
import path from 'path';

import React from 'react';
import { renderToString } from 'react-dom/server';
import { Provider } from 'react-redux';
import { getState } from 'redux';
import { match, RouterContext } from 'react-router'

import routes from './app/routes';
import store from './app/store';

const app = express();
const port         = 666;

app.get('/', (req, res) => {
  match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
    if (renderProps) {
      console.log(renderProps.components);
      res.send(renderToString(
        <Provider store={store}>
          <RouterContext {...renderProps} />
        </Provider>
      ))
    } else {
      console.log('err')
    }
  });
});

app.use('/public', express.static(path.join(__dirname, 'public')));
app.use('/data', express.static(path.join(__dirname, 'data')));

app.listen(port);
console.log(`localhost:${port}`);

routes.js

import React from 'react';
import { Router, Route, browserHistory } from 'react-router';

import App from './Components/App';

const routes = (
  <Router history={browserHistory}>
    <Route path="/" component={App} />
  </Router>
);

export default routes;

App组件有一个fetch函数:

const fetch = (dispatch) => {
    const endpoint = '/data/index.json';

    axios.get(endpoint).then((res) => {
        Array.prototype.forEach.call(res.data, d => {
            const payload = {
                id:    d.id,
                title: d.title
            };

            dispatch({type: 'ADD_CONTENT', payload});
        });
    });
};

这个函数派发了“ADD_CONTENT”动作:

case 'ADD_CONTENT':
  return { ...state, data: [...state.data, action.payload] };

所有工作在客户端都很好。

1个回答

0
  1. 你的redux actions应该返回一个promise,当store正确加载数据时,该promise应该被执行

例如:

const fetch = (dispatch) => {
    const endpoint = '/data/index.json';

    return axios.get(endpoint).then((res) => {
        Array.prototype.forEach.call(res.data, d => {
            const payload = {
                id:    d.id,
                title: d.title
            };

            dispatch({type: 'ADD_CONTENT', payload});
        });
    });
};
  1. 你必须使用类似 redux-connect 的包装器,在加载组件之前调用这些操作。

在你的例子中,路由组件是 App

在这种情况下,你需要使用所需的 redux 操作来装饰该组件,以预加载数据。

import { asyncConnect } from 'redux-connect';

@asyncConnect([{
    promise: ({ store: { dispatch } }) => {
        // `fetch` is your redux action returning a promise
        return dispatch(fetch());
    }
}])
@connect(
    state => ({
        // At this point, you can access your redux data as the
        // fetch will have finished 
        data: state.myReducer.data
    })
)
export default class App extends Component {
    // ...the `data` prop here is preloaded even on the server
}

@connect 可以修饰嵌套组件,如果您不使用顶级容器中的数据,因为 store 在应用程序范围内可用,但是 @asyncConnect 必须修饰由 react-router 直接调用的组件,它不能在呈现树中的嵌套组件上使用(至少目前还不支持),否则它们将在初始渲染期间不会被解析

由于组件树的渲染仅在服务器端发生一次,因此该函数无法由组件本身调用,需要在加载之前通过 redux-connect 和支持 promises 的 redux action 调用,例如使用 redux-thunk 中间件。


谢谢你提供关于redux-connect的信息,但我必须承认他们的示例非常令人困惑。我已经得到了服务器路径,但组件路径对我来说非常奇怪。我应该如何使用@asyncConnect连接我的数据?我应该放弃我的reducers并仅使用reduxAsyncConnect吗? - Tomek Buszewski
在你的主 combineReducers(...) 调用中,只需包含来自 redux-connect 的 reducer,而不会对其他 reducer 产生任何影响。我不知道它是否是强制性的,也许是为了跟踪预加载状态,我个人在我的布局中使用它来显示“加载中”消息,当客户端发生加载时,即在第一次 SSR 后的所有后续路由更改。 - Pandaiolo
但我同意它不容易使用,需要一点尝试 :-) 不过我并没有发现更简单的方法。(几个月前) - Pandaiolo
好的,那么第一个问题是 - 我该如何在现有代码中使用 asyncConnect - Tomek Buszewski
@connect 来自 react-redux 库,它是我在使用 react 和 redux 的方法,不知道是否还有其他的方法?就像这个教程中所解释的那样:http://www.jchapron.com/2015/08/14/getting-started-with-redux/#3buildingtheui。你可以将它作为组件导出时的函数,而不是装饰器。(请参考 react-redux 文档,它们使用的是这种方式) - Pandaiolo
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接