Jon*_*Jon 58 performance render-to-string reactjs isomorphic-javascript react-dom
我注意到reactDOM.renderToString()
在服务器上渲染大型组件树时,该方法开始显着减慢.
一点背景.该系统是一个完全同构的堆栈.最高级别的App
组件呈现模板,页面,dom元素和更多组件.查看反应代码,我发现它渲染了~1500个组件(这包括任何简单的dom标记,它被视为一个简单的组件,<p>this is a react component</p>
.
在开发中,渲染~1500个组件需要大约200-300ms.通过删除一些组件,我能够在~175-225ms内获得~1200个组件.
在生产中,〜1500个组件上的renderToString大约需要50-200ms.
时间似乎是线性的.没有一个组件是慢的,而是它的总和.
这会在服务器上产生一些问题.冗长的方法导致服务器响应时间过长.TTFB比它应该高很多.使用api调用和业务逻辑,响应应该是250ms,但是使用250ms renderToString它会加倍!SEO和用户不好.此外,作为同步方法,renderToString()
可以阻止节点服务器并备份后续请求(这可以通过使用2个单独的节点服务器来解决:1作为Web服务器,1作为服务来单独呈现反应).
理想情况下,生产中需要5-50ms的renderToString.我一直在研究一些想法,但我不确定最好的方法是什么.
任何标记为"静态"的组件都可以缓存.通过使用呈现的标记保持缓存,renderToString()
可以在呈现之前检查缓存.如果找到一个组件,它会自动抓取该字符串.在高级组件中执行此操作将保存所有嵌套子组件的安装.您必须使用当前的rootID替换缓存的组件标记的反应rootID.
通过将组件定义为"简单",react应该能够在呈现时跳过所有生命周期方法.反应已经这样做了芯反应,DOM组件(<p/>
,<h1/>
,等).很高兴扩展自定义组件以使用相同的优化.
服务器上不需要返回的组件(没有SEO值)可以简单地跳过.客户端加载后,设置一个clientLoaded
标志true
并将其传递给强制重新渲染.
到目前为止,我实现的唯一解决方案是减少服务器上呈现的组件数量.
我们正在研究的一些项目包括:
有人遇到过类似的问题吗?你有什么能做的?谢谢.
Fed*_*ico 13
使用react-router1.0和react0.14,我们错误地多次序列化我们的flux对象.
RoutingContext
将调用createElement
您的react路由器路由中的每个模板.这允许你注入你想要的任何道具.我们也使用助焊剂.我们发送一个大型对象的序列化版本.在我们的例子中,我们flux.serialize()
在createElement中进行.序列化方法可能需要大约20ms.使用4个模板,这将是您的renderToString()
方法额外的80毫秒!
旧代码:
function createElement(Component, props) {
props = _.extend(props, {
flux: flux,
path: path,
serializedFlux: flux.serialize();
});
return <Component {...props} />;
}
var start = Date.now();
markup = renderToString(<RoutingContext {...renderProps} createElement={createElement} />);
console.log(Date.now() - start);
Run Code Online (Sandbox Code Playgroud)
轻松优化到此:
var serializedFlux = flux.serialize(); // serialize one time only!
function createElement(Component, props) {
props = _.extend(props, {
flux: flux,
path: path,
serializedFlux: serializedFlux
});
return <Component {...props} />;
}
var start = Date.now();
markup = renderToString(<RoutingContext {...renderProps} createElement={createElement} />);
console.log(Date.now() - start);
Run Code Online (Sandbox Code Playgroud)
在我的情况下,这有助于将renderToString()
时间从约120毫秒减少到约30毫秒.(你仍然需要serialize()
在总数之前加上1x 的~20ms,renderToString()
这是一个很好的快速改进. - 即使你不知道直接的影响,记住要始终正确地做事是很重要的!
更新1:我在底部添加了一个完整的工作示例.它将组件缓存在内存和更新中data-reactid
.
这实际上可以很容易地完成.您应该修补 ReactCompositeComponent
并检查缓存版本:
import ReactCompositeComponent from 'react/lib/ReactCompositeComponent';
const originalMountComponent = ReactCompositeComponent.Mixin.mountComponent;
ReactCompositeComponent.Mixin.mountComponent = function() {
if (hasCachedVersion(this)) return cache;
return originalMountComponent.apply(this, arguments)
}
Run Code Online (Sandbox Code Playgroud)
您应该require('react')
在应用程序的任何位置之前执行此操作.
Webpack注意:如果您使用类似的东西new webpack.ProvidePlugin({'React': 'react'})
,应将其更改为new webpack.ProvidePlugin({'React': 'react-override'})
您进行修改react-override.js
和导出的位置react
(即module.exports = require('react')
)
在内存和更新reactid
属性中缓存的完整示例可以是:
import ReactCompositeComponent from 'react/lib/ReactCompositeComponent';
import jsan from 'jsan';
import Logo from './logo.svg';
const cachable = [Logo];
const cache = {};
function splitMarkup(markup) {
var markupParts = [];
var reactIdPos = -1;
var endPos, startPos = 0;
while ((reactIdPos = markup.indexOf('reactid="', reactIdPos + 1)) != -1) {
endPos = reactIdPos + 9;
markupParts.push(markup.substring(startPos, endPos))
startPos = markup.indexOf('"', endPos);
}
markupParts.push(markup.substring(startPos))
return markupParts;
}
function refreshMarkup(markup, hostContainerInfo) {
var refreshedMarkup = '';
var reactid;
var reactIdSlotCount = markup.length - 1;
for (var i = 0; i <= reactIdSlotCount; i++) {
reactid = i != reactIdSlotCount ? hostContainerInfo._idCounter++ : '';
refreshedMarkup += markup[i] + reactid
}
return refreshedMarkup;
}
const originalMountComponent = ReactCompositeComponent.Mixin.mountComponent;
ReactCompositeComponent.Mixin.mountComponent = function (renderedElement, hostParent, hostContainerInfo, transaction, context) {
return originalMountComponent.apply(this, arguments);
var el = this._currentElement;
var elType = el.type;
var markup;
if (cachable.indexOf(elType) > -1) {
var publicProps = el.props;
var id = elType.name + ':' + jsan.stringify(publicProps);
markup = cache[id];
if (markup) {
return refreshMarkup(markup, hostContainerInfo)
} else {
markup = originalMountComponent.apply(this, arguments);
cache[id] = splitMarkup(markup);
}
} else {
markup = originalMountComponent.apply(this, arguments)
}
return markup;
}
module.exports = require('react');
Run Code Online (Sandbox Code Playgroud)
它不是完整的解决方案,我有同样的问题,我的反应同构应用程序,我使用了几件事.
1)在nodejs服务器前使用Nginx,并在短时间内缓存渲染的响应.
2)在显示项目列表的情况下,我仅使用列表的子集.例如,我将仅渲染X项以填充视口,并使用Websocket或XHR在客户端加载列表的其余部分.
3)我的一些组件在服务器端呈现中是空的,只会从客户端代码(componentDidMount)加载.这些组件通常是图形或配置文件相关的组件.这些组件通常在SEO的观点上没有任何好处
4)关于SEO,从我的经验6个月与同构应用程序.Google Bot可以轻松读取客户端React Web页面,因此我不确定为什么我们会厌烦服务器端渲染.
5)保持<Head >
和<Footer>
作为静态字符串或使用模板引擎(Reactjs-handellbars),并仅渲染页面的内容(它应该保存一些渲染组件).如果是单页应用,您可以更新每个导航内的标题说明Router.Run
.
归档时间: |
|
查看次数: |
21286 次 |
最近记录: |