1

我正在构建一个通用应用程序,React它通过和Redux在服务器端呈现。NodeJSExpressJS

一切正常,Express 处理程序调用{match} from 'react-router'并且new store instance每次都会创建一个。

我遇到的问题是:{renderToString} from 'react-dom/server'只呈现pristine商店的版本,如果有东西改变了商店(例如:通过 调度的动作componentWillMount),商店将被更新,但在调用generated markup新的之前不会改变。renderToString

  1. 我不知道减速器将如何(按请求)更改状态,因此,我无法在调用之前提供初始状态renderToString
  2. 我想避免再renderToString打电话。

这是我的示例代码:

const store = createStore(
  reducers,

  // an object that provides the initial generic state
  res.viewModel.__INITIAL_STATE__ || Object.create(null),

  middlewares
);

// now the store is pristine and calling store.getState()
// I will retrieve an invalid version of the state

const markup = renderToString(
  <Provider store={store}>
    {<RouterContext {...renderProps} />}
  </Provider>
);

// now the store is correctly computed and calling
// store.getState() gets the right version but the markup
// generated is still old. Only recalling `renderToString` will get 
// the right markup

const markup2 = renderToString(
  <Provider store={store}>
    {<RouterContext {...renderProps} />}
  </Provider>
);
4

2 回答 2

1

另一种找到的方法:

使用 Redux-Saga

https://medium.com/@navgarcha7891/react-server-side-rendering-with-simple-redux-store-hydration-9f77ab66900a#.xin76jnk4

优点

  1. 避免特定的server-side附加代码
  2. 保持 Redux 方式

缺点

  1. 调用renderToString两次

例子:

import { END } from 'redux-saga';

store.runSaga = sagaMiddleware.run;
store.close = () => store.dispatch(END);


// server.js
store.runSaga(sagas).done.then(() => {
    const state = store.getState();
    const html = renderToString(rootComponent);

    reply(renderApplication({state, html}));
});

renderToString(rootComponent);

store.close();
于 2017-01-09T09:40:26.267 回答
1

一种方法是在您的 Route 组件上提供每个路由操作作为静态方法,然后在调用之前由您的快速中间件调用,renderToString例如

class Index extends Component {
   componentDidMount() {
       // NOTE: The client fetches the data in `componentDidMount` instead of `componentWillMount` to ensure we don't duplicate efforts on the server.
       const { dispatch } = this.props;
       Index.fetchData({ dispatch });
   }

   render() {
       ...
   }
}

Index.fetchData = function({ dispatch }) {
    // Returns promise via redux-thunk / redux-promise
    return dispatch(fetchDataIfNeeded());
};

export default connect(state => state)(Index);

然后在您的中间件中,您将fetchData在渲染之前调用匹配路由组件上定义的所有静态方法,例如

app.use((req, res) => {
    match({
        routes,
        location: req.url
    }, (error, redirectLocation, renderProps) => {
        const store = configureStore();
        const requests = renderProps.components
            .map(component => {
                // Handle `connect`ed components.
                if (component.WrappedComponent) {
                    component = component.WrappedComponent;
                }
                if (component.fetchData) {
                    return component.fetchData({
                        dispatch: store.dispatch
                    })
                    .catch(() => {});
                }
            });
        Promise.all(requests)
            .then(() => {
                // Store now contains all necessary state.
                const markup = renderToString(
                    <Provider store={store}>
                        <RouterContext {...renderProps} />
                    </Provider>
                );
                res.send(`<!doctype html><html><body>${markup}</body></html>`);
            });
    });
});

您可以在60frames 样板中看到一个完整的示例。

相关模块:

https://github.com/60frames/react-boilerplate/blob/master/src/components/index/Index.js

https://github.com/60frames/react-boilerplate/blob/master/src/server.js

于 2017-01-08T13:09:19.880 回答