我在Route组件上使用static fetchData
方法。。。
const mapStateToProps = (state) => ({
posts: state.posts
})
@connect(mapStateToProps)
class Blog extends Component {
static fetchData (dispatch) {
return dispatch(fetchPosts())
}
render () {
return (
<PostsList posts={this.props.posts} />
)
}
}
并在服务器端收集初始渲染之前的所有承诺。。。
match({ routes, location }, (error, redirectLocation, renderProps) => {
const promises = renderProps.components
.filter((component) => component.fetchData)
.map((component) => component.fetchData(store.dispatch))
Promise.all(promises).then(() => {
res.status(200).send(renderView())
})
})
它运行良好,服务器等待,直到我的所有承诺都得到解决,然后再渲染应用程序。
现在,在我的客户端脚本上,我正在执行与服务器上类似的操作。。。
...
function resolveRoute (props) {
props.components
.filter((component) => component.fetchData)
.map((component) => component.fetchData(store.dispatch))
return <RouterContext {...props} />
}
render((
<Provider store={store}>
<Router
history={browserHistory}
routes={routes}
render={resolveRoute} />
</Provider>
), document.querySelector('#app'))
而且效果很好。但是,正如您可能推断的那样,在初始页面呈现中,静态fetchData
被调用了两次(一次在服务器上,一次在客户端上),我不希望这样。
关于如何解决这个问题,有什么建议吗?建议?
我是在手机上输入的,所以我为没有格式化而道歉。
对于我的项目,我正在做一些与你类似的事情;我有一个静态的fetchData方法,我循环使用renderProps中的组件,然后调用静态方法,等待promise解析。
然后,我从redux存储中调用get state,字符串化它,并将它传递给服务器上的render函数,这样它就可以在客户端上呈现出初始状态对象。
从客户端,我只需获取初始状态变量并将其传递给redux存储。然后,Redux将处理让您的客户端存储与服务器上的存储相匹配的问题。从那里,你只需将你的商店交给供应商,然后照常进行。您根本不需要在客户端上调用静态方法。
作为我所说的一个例子,您可以查看我的github项目,代码会自行解释。https://github.com/mr-antivirus/riur
希望有帮助!
[编辑]这是代码!
Client.js
'use strict'
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { Router, browserHistory } from 'react-router';
import createStore from '../shared/store/createStore';
import routes from '../shared/routes';
const store = createStore(window.__app_data);
const history = browserHistory;
render (
<Provider store={store}>
<Router history={history} routes={routes} />
</Provider>,
document.getElementById('content')
)
Server.js
app.use((req, res, next) => {
match({ routes, location:req.url }, (err, redirectLocation, renderProps) => {
if (err) {
return res.status(500).send(err);
}
if (redirectLocation) {
return res.redirect(302, redirectLocation.pathname + redirectLocation.search);
}
if (!renderProps) {
return next();
}
// Create the redux store.
const store = createStore();
// Retrieve the promises from React Router components that have a fetchData method.
// We use this data to populate our store for server side rendering.
const fetchedData = renderProps.components
.filter(component => component.fetchData)
.map(component => component.fetchData(store, renderProps.params));
// Wait until ALL promises are successful before rendering.
Promise.all(fetchedData)
.then(() => {
const asset = {
javascript: {
main: '/js/bundle.js'
}
};
const appContent = renderToString(
<Provider store={store}>
<RouterContext {...renderProps} />
</Provider>
)
const isProd = process.env.NODE_ENV !== 'production' ? false : true;
res.send('<!doctype html>' + renderToStaticMarkup(<Html assets={asset} content={appContent} store={store} isProd={isProd} />));
})
.catch((err) => {
// TODO: Perform better error logging.
console.log(err);
});
});
});
RedditContainer.js
class Reddit extends Component {
// Used by the server, ONLY, to fetch data
static fetchData(store) {
const { selectedSubreddit } = store.getState();
return store.dispatch(fetchPosts(selectedSubreddit));
}
// This will be called once on the client
componentDidMount() {
const { dispatch, selectedSubreddit } = this.props;
dispatch(fetchPostsIfNeeded(selectedSubreddit));
}
... Other methods
};
HTML.js
'use strict';
import React, { Component, PropTypes } from 'react';
import ReactDom from 'react-dom';
import Helmet from 'react-helmet';
import serialize from 'serialize-javascript';
export default class Layout extends Component {
static propTypes = {
assets: PropTypes.object,
content: PropTypes.string,
store: PropTypes.object,
isProd: PropTypes.bool
}
render () {
const { assets, content, store, isProd } = this.props;
const head = Helmet.rewind();
const attrs = head.htmlAttributes.toComponent();
return (
<html {...attrs}>
<head>
{head.base.toComponent()}
{head.title.toComponent()}
{head.meta.toComponent()}
{head.link.toComponent()}
{head.script.toComponent()}
<link rel='shortcut icon' href='/favicon.ico' />
<meta name='viewport' content='width=device-width, initial-scale=1' />
</head>
<body>
<div id='content' dangerouslySetInnerHTML={{__html: content}} />
<script dangerouslySetInnerHTML={{__html: `window.__app_data=${serialize(store.getState())}; window.__isProduction=${isProd}`}} charSet='utf-8' />
<script src={assets.javascript.main} charSet='utf-8' />
</body>
</html>
);
}
};
重申。。。
- 在客户端上,获取状态变量并将其传递给您的商店
- 在服务器上,循环调用fetchData并传递存储的组件。等待承诺得到解决,然后兑现
- 在HTML.js(renderView函数)中,序列化Redux存储并将输出呈现为客户端的javascript变量
- 在React组件中,为要调用的服务器ONLY创建一个静态fetchData方法。安排你需要的行动
您可以从fbjs模块使用CanUseDOM。
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
//only render on the server because it doesn't have DOM
if(!canUseDOM)
static fetch here
一个更好的想法是将服务器端的存储状态脱水,并将客户端的初始存储状态与脱水状态水合。
来自Redux文档:
这使得创建通用应用程序变得容易,因为服务器中的状态可以序列化并水合到客户端中,而无需额外的编码工作。
http://redux.js.org/docs/introduction/ThreePrinciples.html