Компонент не получает реквизит (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 позволят быть таковым и выполнять произвольную последовательность обращений к источникам данных в асинхронном режиме.