[英]Strategies for server-side rendering of asynchronously initialized React.js components
React.js的一大优势应该是服务器端渲染 。 问题是关键函数React.renderComponentToString()
是同步的,这使得在服务器上呈现组件层次结构时无法加载任何异步数据。
假设我有一个用于评论的通用组件,我可以在页面的任何位置删除它。 它只有一个属性,某种标识符(例如文章的id在其下面放置注释),其他所有内容都由组件本身处理(加载,添加,管理注释)。
我非常喜欢Flux架构,因为它使很多东西变得更容易,而且它的存储非常适合在服务器和客户端之间共享状态。 一旦我的包含注释的商店被初始化,我就可以将其序列化并将其从服务器发送到客户端,在那里可以轻松恢复。
问题是填充我的商店的最佳方式是什么。 在过去的几天里,我一直在谷歌搜索,我遇到了一些策略,考虑到React的这个功能被“提升”了多少,这些策略似乎都不是很好。
在我看来,最简单的方法是在实际渲染开始之前填充我的所有商店。 这意味着在组件层次结构之外的某个地方(例如,挂钩到我的路由器)。 这种方法的问题是我必须两次定义页面结构。 考虑一个更复杂的页面,例如一个包含许多不同组件的博客页面(实际的博客文章,评论,相关帖子,最新帖子,推特流......)。 我必须使用React组件设计页面结构,然后在其他地方我必须定义为当前页面填充每个所需存储的过程。 这似乎不是一个很好的解决方案。 不幸的是,大多数同构教程都是以这种方式设计的(例如,这个伟大的通量教程 )。
React-async 。 这种方法很完美。 它让我只需在每个组件的特殊函数中定义如何初始化状态(无论是同步还是异步),并在将层次结构呈现为HTML时调用这些函数。 它的工作方式是在状态完全初始化之前不会呈现组件。 问题是它需要Fibers ,据我所知,这是一个改变标准JavaScript行为的Node.js扩展。 虽然我非常喜欢这个结果,但在我看来,我们并没有找到解决方案,而是改变了游戏规则。 而且我认为我们不应该被迫这样做来使用React.js的这个核心功能。 我也不确定这个解决方案的一般支持。 是否可以在标准Node.js虚拟主机上使用Fiber?
我自己在思考一点。 我没有真正想过通过实现细节,但一般的想法是我会以类似于React-async的方式扩展组件,然后我会在根组件上重复调用React.renderComponentToString()。 在每次传递过程中,我会收集扩展回调,然后在传递和传递中调用它们来填充商店。 我将重复此步骤,直到填充当前组件层次结构所需的所有存储。 有很多事情需要解决,我对性能特别不确定。
我错过了什么? 还有其他方法/解决方案吗? 现在我正在考虑采用react-async / fiber方式,但我并不完全确定它,如第二点所述。
关于GitHub的相关讨论 。 显然,没有官方方法甚至解决方案。 也许真正的问题是如何使用React组件。 像简单的视图层(几乎是我的建议第一)或像真正的独立和独立组件?
如果你使用react-router ,你可以在组件中定义一个willTransitionTo
方法,它们会传递一个你可以调用.wait
的Transition
对象。
如果renderToString是同步的并不重要,因为在解析所有.wait
ed promise之前不会调用对Router.run
的回调,因此在中间件中调用renderToString
你可以填充存储。 即使存储是单例,您也可以在同步呈现调用之前暂时及时设置其数据,组件将看到它。
中间件示例:
var Router = require('react-router');
var React = require("react");
var url = require("fast-url-parser");
module.exports = function(routes) {
return function(req, res, next) {
var path = url.parse(req.url).pathname;
if (/^\/?api/i.test(path)) {
return next();
}
Router.run(routes, path, function(Handler, state) {
var markup = React.renderToString(<Handler routerState={state} />);
var locals = {markup: markup};
res.render("layouts/main", locals);
});
};
};
routes
对象(描述路由层次结构)与客户端和服务器逐字共享
我知道这可能不是你想要的,它可能没有意义,但我记得通过轻微修改组件来处理两者:
所以类似于:
/** @jsx React.DOM */
var UserGist = React.createClass({
getInitialState: function() {
if (this.props.serverSide) {
return this.props.initialState;
} else {
return {
username: '',
lastGistUrl: ''
};
}
},
componentDidMount: function() {
if (!this.props.serverSide) {
$.get(this.props.source, function(result) {
var lastGist = result[0];
if (this.isMounted()) {
this.setState({
username: lastGist.owner.login,
lastGistUrl: lastGist.html_url
});
}
}.bind(this));
}
},
render: function() {
return (
<div>
{this.state.username}'s last gist is
<a href={this.state.lastGistUrl}>here</a>.
</div>
);
}
});
// On the client side
React.renderComponent(
<UserGist source="https://api.github.com/users/octocat/gists" />,
mountNode
);
// On the server side
getTheInitialState().then(function (initialState) {
var renderingOptions = {
initialState : initialState;
serverSide : true;
};
var str = Xxx.renderComponentAsString( ... renderingOptions ...)
});
对不起,我手边没有确切的代码,所以这可能不是开箱即用的,但我是为了讨论而发布的。
同样,我们的想法是将大部分组件视为一个愚蠢的视图,并尽可能多地从组件中获取数据。
今天我真的很乱,虽然这不是你的问题的答案,但我已经使用了这种方法。 我想使用Express进行路由而不是React Router,我不想使用Fibers,因为我不需要节点中的线程支持。
所以我只是决定对于需要在加载时将其呈现给磁通存储的初始数据,我将执行AJAX请求并将初始数据传递到存储中
我在这个例子中使用Fluxxor。
所以在我的快速路线上,在这种情况下是/products
路线:
var request = require('superagent');
var url = 'http://myendpoint/api/product?category=FI';
request
.get(url)
.end(function(err, response){
if (response.ok) {
render(res, response.body);
} else {
render(res, 'error getting initial product data');
}
}.bind(this));
然后我初始化渲染方法,将数据传递给商店。
var render = function (res, products) {
var stores = {
productStore: new productStore({category: category, products: products }),
categoryStore: new categoryStore()
};
var actions = {
productActions: productActions,
categoryActions: categoryActions
};
var flux = new Fluxxor.Flux(stores, actions);
var App = React.createClass({
render: function() {
return (
<Product flux={flux} />
);
}
});
var ProductApp = React.createFactory(App);
var html = React.renderToString(ProductApp());
// using ejs for templating here, could use something else
res.render('product-view.ejs', { app: html });
我知道这个问题是在一年前问的,但是我们遇到了同样的问题,我们用嵌套的promises来解决它,这些promises是从将要渲染的组件派生出来的。 最后,我们获得了应用程序的所有数据,并将其发送到了最后。
例如:
var App = React.createClass({
/**
*
*/
statics: {
/**
*
* @returns {*}
*/
getData: function (t, user) {
return Q.all([
Feed.getData(t),
Header.getData(user),
Footer.getData()
]).spread(
/**
*
* @param feedData
* @param headerData
* @param footerData
*/
function (feedData, headerData, footerData) {
return {
header: headerData,
feed: feedData,
footer: footerData
}
});
}
},
/**
*
* @returns {XML}
*/
render: function () {
return (
<label>
<Header data={this.props.header} />
<Feed data={this.props.feed}/>
<Footer data={this.props.footer} />
</label>
);
}
});
并在路由器中
var AppFactory = React.createFactory(App);
App.getData(t, user).then(
/**
*
* @param data
*/
function (data) {
var app = React.renderToString(
AppFactory(data)
);
res.render(
'layout',
{
body: app,
someData: JSON.stringify(data)
}
);
}
).fail(
/**
*
* @param error
*/
function (error) {
next(error);
}
);
想与我分享我使用Flux
进行服务器端渲染的方法,例如:
假设我们有来自商店的初始数据的component
:
class MyComponent extends Component { constructor(props) { super(props); this.state = { data: myStore.getData() }; } }
如果class需要一些初始状态的预加载数据,那么让我们为MyComponent
创建Loader:
class MyComponentLoader { constructor() { myStore.addChangeListener(this.onFetch); } load() { return new Promise((resolve, reject) => { this.resolve = resolve; myActions.getInitialData(); }); } onFetch = () => this.resolve(data); }
商店:
class MyStore extends StoreBase { constructor() { switch(action => { case 'GET_INITIAL_DATA': this.yourFetchFunction() .then(response => { this.data = response; this.emitChange(); }); break; } getData = () => this.data; }
现在只需在路由器中加载数据:
on('/my-route', async () => { await new MyComponentLoader().load(); return <MyComponent/>; });
就像一个简短的汇总 - > GraphQL将为你的堆栈解决这个问题...
- > getDataFromTree将自动在您的应用程序中查找所有相关查询并执行它们,在服务器上放置您的apollo缓存,从而实现完全正常工作的SSR..BÄM
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.