Компонент не получает реквизит (React SSR)

У меня есть вилка Redux Saga, и я пытаюсь обновить хранилище на стороне сервера действием. Это идет хорошо, но компонент не обновляется (не вызывайте mapStateToProps), когда хранилище было обновлено. В чем дело? Помогите, пожалуйста.

Журнал сервера

Источник компонента:

import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { testAction } from '../../actions';

class Event extends Component {
  static propTypes = {
    title: PropTypes.string,
    testAction: PropTypes.func
  }
  componentWillMount() {
    this.props.testAction({ test: 'test' });
  }
  render() {
    return (
      <div>
        Event - {this.props.title}
      </div>
    );
  }
}

function mapStateToProps(state) {
  console.log(state);
  return {
    title: state.default.test
  };
}

export default connect(
  mapStateToProps,
  { testAction }
)(Event);

server.js источник:

import Express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import favicon from 'serve-favicon';
import compression from 'compression';
import http from 'http';
import proxy from 'express-http-proxy';
import path from 'path';
import url from 'url';
import { match, createMemoryHistory } from 'react-router';

import config from './config';
import configureStore from './store/configureStore';
import Html from './helpers/Html';
import getRoutes from './routes';
import waitAll from './sagas/waitAll';
import { Root } from 'containers';

const app = new Express();
const server = new http.Server(app);

// disable `X-Powered-By` HTTP header
app.disable('x-powered-by');

app.use(compression());
app.use(favicon(path.join(__dirname, '..', 'static', 'favicon.ico')));
app.use(Express.static(path.join(__dirname, '..', 'static')));

// Proxy to API
app.use('/api', proxy(config.apiBaseUrl, {
  // eslint-disable-next-line
  forwardPath: (req, res) => url.parse(req.url).path
}));

app.use((req, res) => {
  if (__DEVELOPMENT__) {
    webpackIsomorphicTools.refresh();
  }

  const memoryHistory = createMemoryHistory();
  const store = configureStore();
  const allRoutes = getRoutes(store);
  const assets = webpackIsomorphicTools.assets();

  function hydrateOnClient() {
    const htmlComponent = <Html assets={assets} store={store} />;
    const renderedDomString = ReactDOMServer.renderToString(htmlComponent);
    res.send(`<!doctype html>\n ${renderedDomString}`);
  }

  if (__DISABLE_SSR__) {
    hydrateOnClient();
    return;
  }

  match({ routes: allRoutes, location: req.url }, (error, redirectLocation, renderProps) => {
    if (redirectLocation) {
      res.redirect(redirectLocation.pathname + redirectLocation.search);
    } else if (error) {
      console.error('ROUTER ERROR:', error);
      res.status(500);
      hydrateOnClient();
    } else if (renderProps) {
      const preloaders = renderProps.components
      .filter((component) => component && component.preload)
      .map((component) => component.preload(renderProps.params, req))
      .reduce((result, preloader) => result.concat(preloader), []);

      const runTasks = store.runSaga(waitAll(preloaders));

      runTasks.done.then(() => {
        const rootComponent = (<Root
          store={store}
          routes={allRoutes}
          history={memoryHistory}
          renderProps={renderProps}
          type="server"
        />);
        const htmlComponent = <Html assets={assets} component={rootComponent} store={store} />;
        const renderedDomString = ReactDOMServer.renderToString(htmlComponent);

        global.navigator = { userAgent: req.headers['user-agent'] };
        res.status(200).send(`<!doctype html>\n ${renderedDomString}`);
      }).catch((e) => {
        console.log(e.stack);
      });

      store.close();
    } else {
      res.status(404).send('Not found');
    }
  });
});

if (config.port) {
  server.listen(config.port, (err) => {
    if (err) {
      console.error(err);
    }
    console.info('==>   Open http://%s:%s in a browser to view the app.', config.host, config.port);
  });
} else {
  console.error('==>     ERROR: No PORT environment variable has been specified');
}

1 ответ

В общем, если вы используете рендеринг на стороне сервера, тогда будет гораздо проще и эффективнее заранее собрать начальное состояние и просто передать его клиенту в виде строки.

Стандартная схема работы примерно следующая: у вас есть несколько функций обещаний, которые получают данные из некоторых источников. Когда требуется адрес с уже работающей страницы на клиентах, вы просто запускаете сагу при получении и экспресс-трансляцию ответа от источника.

А в случае SSR вы просто заранее вызываете сводные обещания для всех операций и отправляете его в виде сериализованного объекта. Пример:

Promise.all([ getData1(), getData2() ]).then((initialState) => {
    const store = createStore(handlers, initialState);
    // .....
    const htmlComponent = <Html assets={assets} component={rootComponent} store={store} />;
    const renderedDomString = ReactDOMServer.renderToString(htmlComponent);
})

Заметьте: в будущих версиях React с приходом движка Fiber обещают асинхронный рендеринг, что функции ReactDOMServer.renderToString уже Promise позволят быть таковым и выполнять произвольную последовательность обращений к источникам данных в асинхронном режиме.

Другие вопросы по тегам