首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >React renderToString()性能和缓存React组件

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

Stack Overflow用户
提问于 2016-01-12 02:55:58
回答 4查看 24.1K关注 0票数 62

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

背景

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

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

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

时间看起来确实是线性的。没有一个组件是慢的,而是许多组件的总和。

问题

这会在服务器上产生一些问题。冗长的方法会导致服务器响应时间过长。TTFB比它应该的要高得多。对于api调用和业务逻辑,响应应该是250ms,但是对于250ms的renderToString,它是双倍的!对SEO和用户都不好。此外,作为一种同步方法,renderToString()可以阻止节点服务器并备份后续请求(这可以通过使用两个单独的节点服务器来解决:一个作为web服务器,另一个作为单独呈现react的服务)。

尝试

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

想法1:缓存组件

任何标记为“static”的组件都可以被缓存。通过为呈现的标记保留缓存,renderToString()可以在呈现之前检查缓存。如果它找到一个组件,它会自动抓取字符串。在高级组件中执行此操作将节省所有嵌套子组件的挂载。您必须用当前的rootID替换缓存的组件标记的react rootID。

想法2:将组件标记为简单/愚蠢

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

想法3:跳过服务器端呈现的组件

服务器不需要返回的组件(没有SEO值)可以在服务器上简单地跳过。加载客户端后,将clientLoaded标志设置为true并将其向下传递以强制重新渲染。

关闭和其他尝试

到目前为止,我实现的唯一解决方案是减少在服务器上呈现的组件数量。

我们正在研究的一些项目包括:

有没有人遇到过类似的问题?你能做些什么呢?谢谢。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2016-01-21 01:57:51

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

RoutingContext将为您的react-router路由中的每个模板调用createElement。这允许你注入任何你想要的道具。我们也使用flux。我们发送一个大对象的序列化版本。在我们的例子中,我们在createElement中执行flux.serialize()。序列化方法可能需要大约20ms。有了4个模板,你的renderToString()方法就多了80ms!

旧代码:

代码语言:javascript
复制
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);

很容易优化到这一点:

代码语言:javascript
复制
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()之前)它是一个很好的快速改进。--重要的是要记住始终正确地做事情,即使您不知道直接的影响!

票数 13
EN

Stack Overflow用户

发布于 2016-07-25 16:29:53

想法1:缓存组件

更新1:我在底部添加了一个完整的工作示例。它在内存中缓存组件并更新data-reactid

这实际上可以很容易地完成。您应该对ReactCompositeComponent执行monkey-patch操作并检查缓存的版本:

代码语言:javascript
复制
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)
}

在你的应用中require('react') anywhere之前,你应该这样做。

Webpack注意:如果你使用像new webpack.ProvidePlugin({'React': 'react'})这样的东西,你应该将它改为new webpack.ProvidePlugin({'React': 'react-override'}),在react-override.js中进行修改,并导出react (即module.exports = require('react'))。

在内存中缓存并更新reactid属性的完整示例可能是:

代码语言:javascript
复制
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');
票数 8
EN

Stack Overflow用户

发布于 2016-01-17 14:25:33

这不是一个完整的解决方案我遇到了同样的问题,我的react同构应用程序,我使用了几个东西。

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

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

  1. 我的一些组件在服务器端渲染中是空的,只能从客户端代码(componentDidMount)加载。这些组件通常是与图表或配置文件相关的组件。从

的角度来看,这些组件通常不会从搜索引擎优化中获益

  1. 关于搜索引擎优化,从我使用同构应用程序的6个月的经验来看。Google Bot可以很容易地读取客户端React网页,所以我不确定我们为什么要费心服务器端渲染。

  1. <Head><Footer>保留为静态字符串或使用模板引擎(Reactjs-handlebars),并且只呈现页面的内容(它应该保存一些呈现的组件)。如果是单页面应用程序,您可以在Router.Run.

内的每个导航中更新标题描述

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/34728962

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档