React renderToString()性能和缓存React组件

我注意到reactDOM.renderToString()在服务器上渲染大型组件树时,该方法开始显着减慢速度。

背景

有点背景。该系统是完全同构的堆栈。最高级别的App组件呈现模板,页面,dom元素和更多组件。查看一下react代码,我发现它呈现了约1500个组件(这包括任何被视为简单组件的简单dom标签<p>this is a react component</p>

在开发中,渲染〜1500个组件大约需要200-300ms。通过删除一些组件,我能够在约175-225毫秒内获得约1200个组件进行渲染。

在生产中,大约1500个组件上的renderToString大约需要50-200ms。

时间确实是线性的。没有任何一个要素是缓慢的,而是许多要素的总和。

问题

这在服务器上产生了一些问题。冗长的方法导致服务器响应时间长。TTFB比应有的要高得多。使用api调用和业务逻辑,响应应该为250毫秒,但是使用250毫秒的renderToString时,响应会加倍!对SEO和用户不利。同样,作为一种同步方法,它renderToString()可以阻止节点服务器并备份后续请求(这可以通过使用2个单独的节点服务器来解决:1个作为Web服务器,而1个作为仅提供反应的服务)。

尝试次数

理想情况下,生产中的renderToString需要5到50毫秒。我一直在研究一些想法,但是我不确定最好的方法是什么。

想法1:缓存组件

任何标记为“静态”的组件都可以缓存。通过保留具有渲染标记renderToString()的缓存可以在渲染之前检查缓存。如果找到一个组件,它将自动获取字符串。在较高级别的组件上执行此操作将节省所有嵌套子组件的安装。您将不得不用当前的rootID替换缓存的组件标记的react rootID。

理念2:将组件标记为简单/哑巴

通过将组件定义为“简单”,react应该能够在渲染时跳过所有生命周期方法。反应已经这样做了芯反应,DOM组件(<p/><h1/>,等)。扩展自定义组件以使用相同的优化会很好。

理念3:跳过服务器端渲染上的组件

Components that do not need to be returned by the server (no SEO value) could simply be skipped on the server. Once the client loads, set a clientLoaded flag to true and pass it down to enforce a re-render.

Closing and other attempts

The only solution I've implemented thus far is to reduce the number of components that are rendered on the server.

Some projects we're looking at include:

Has anybody faced similar issues? What have you been able to do? Thanks.

斯丁米亚小卤蛋2020/03/19 14:35:20

使用react-router1.0和react0.14,我们错误地多次序列化了我们的flux对象。

RoutingContext将要求createElement您的react-router路由中的每个模板。这使您可以注入所需的任何道具。我们也使用通量。我们发送大对象的序列化版本。在我们的例子中,我们flux.serialize()在createElement中进行。序列化方法可能需要20毫秒左右的时间。如果有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);

轻松优化为此:

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);

就我而言,这有助于将renderToString()时间从〜120ms 减少到〜30ms。(您仍然需要将1x serialize()的〜20ms加到总数上,这要在之前完成renderToString())这是一个不错的快速改进。-重要的是要记住,即使您不知道直接的影响,也要始终正确地做事!

JinJin达蒙2020/03/19 14:35:20

它不是完整的解决方案,我的同构同构应用程序也遇到了同样的问题,并且使用了两件事。

1)在nodejs服务器之前使用Nginx,并在短时间内缓存呈现的响应。

2)在显示项目列表的情况下,我仅使用列表的子集。例如,我将仅渲染X个项目以填充视口,并使用Websocket或XHR在客户端加载列表的其余部分。

3)我的一些组件在服务器端渲染中为空,并且仅从客户端代码(componentDidMount)加载。这些组件通常是,图或与个人资料相关的组件。从SEO角度来看,这些组件通常没有任何好处

4)关于SEO,根据我6个月使用同构应用的经验。Google Bot可以轻松阅读客户端React Web页面,所以我不确定为什么我们要烦恼服务器端渲染。

5)保留<Head >and <Footer>作为静态字符串,或使用模板引擎(Reactjs-handellbars),仅渲染页面内容(它应保存一些渲染组件)。如果是单页应用,则可以在中的每个导航中更新标题说明Router.Run