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

Performance React renderToString()性能和缓存React组件,performance,reactjs,isomorphic-javascript,render-to-string,react-dom,Performance,Reactjs,Isomorphic Javascript,Render To String,React Dom,我注意到,在服务器上呈现大型组件树时,reactDOM.renderToString()方法开始显著减慢 背景 一点背景知识。该系统是一个完全同构的堆栈。最高级别的App组件呈现模板、页面、dom元素和更多组件。查看react代码,我发现它呈现了约1500个组件(包括任何被视为简单组件的简单dom标记,这是react组件) 在开发过程中,渲染约1500个组件需要约200-300毫秒。通过删除一些组件,我能够在约175-225毫秒内获得约1200个组件进行渲染 在生产中,约1500个组件上的渲染字

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

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

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

在生产中,约1500个组件上的渲染字符串大约需要50-200ms

时间似乎是线性的,没有一个分量是慢的,而是许多分量的总和

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

尝试 理想情况下,在生产中渲染字符串需要5-50毫秒。我一直在研究一些想法,但我不确定最好的方法是什么

想法1:缓存组件 任何标记为“静态”的组件都可以缓存。通过使用呈现的标记保留缓存,
renderToString()
可以在呈现之前检查缓存。如果找到组件,它会自动获取字符串。在高级组件上执行此操作将保存所有嵌套子组件的装载。您必须将缓存组件标记的react rootID替换为当前rootID

想法2:将组件标记为简单/哑 通过将组件定义为“简单”,react应该能够在呈现时跳过所有生命周期方法。react已经为核心react dom组件(

,等等)执行了此操作。扩展自定义组件以使用相同的优化会很好

想法3:跳过服务器端渲染上的组件 服务器不需要返回的组件(无SEO值)可以在服务器上跳过。一旦客户端加载,将
clientLoaded
标志设置为
true
,并向下传递以强制重新呈现

关闭和其他尝试 到目前为止,我实现的唯一解决方案是减少在服务器上呈现的组件数量

我们正在考虑的一些项目包括:

  • (仍在为测试实施此功能)
  • (这似乎与想法2的思路一致)
有没有人遇到过类似的问题?你能做些什么?
谢谢。

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

  • 在nodejs服务器前面使用Nginx,并在短时间内缓存呈现的响应

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

  • 我的一些组件在服务器端呈现中是空的,将仅从客户端代码加载(
    componentDidMount
    )。 这些组件通常是图形或与概要文件相关的组件。从SEO的角度来看,这些组件通常没有任何好处

  • 关于SEO,根据我6个月使用同构应用程序的经验,Google Bot可以很容易地读取客户端的React网页,所以我不确定我们为什么要麻烦服务器端渲染

  • 保持为静态字符串或使用模板引擎(),并仅呈现页面内容(它应该保存一些呈现的组件)。对于单页应用程序,您可以在
    路由器内的每个导航中更新标题说明。运行


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

    RoutingContext
    将为react路由器路由中的每个模板调用
    createElement
    。这允许您注入任何您想要的道具。我们还使用flux。我们发送一个大对象的序列化版本。在我们的例子中,我们正在执行
    flux.serialize()
    在createElement内。序列化方法可能需要约20ms。如果使用4个模板,则对您的
    renderString()
    方法而言,这将是额外的80ms

    旧代码:

    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);
    
    函数createElement(组件、道具){
    道具=uu.extend(道具{
    通量:通量,
    路径:路径,
    serializedFlux:flux.serialize();
    });
    返回;
    }
    var start=Date.now();
    markup=renderToString();
    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);
    
    var serializedFlux=flux.serialize();//只序列化一次!
    函数createElement(组件、道具){
    道具=uu.extend(道具{
    通量:通量,
    路径:路径,
    serializedFlux:serializedFlux
    });
    返回;
    }
    var start=Date.now();
    markup=renderToString();
    console.log(Date.now()-start);
    
    在我的例子中,这有助于将
    renderToString()
    时间从~120ms减少到~30ms。(您仍然需要将1x
    serialize()
    的~20ms添加到总数中,这发生在
    renderToString()
    之前)这是一个很好的快速改进。--重要的是要记住始终正确地执行操作,即使您不知道其直接影响!

    想法1:缓存组件 更新1import ReactCompositeComponent from 'react/lib/ReactCompositeComponent'; const originalMountComponent = ReactCompositeComponent.Mixin.mountComponent; ReactCompositeComponent.Mixin.mountComponent = function() { if (hasCachedVersion(this)) return cache; return originalMountComponent.apply(this, arguments) }
    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');
    
    var ReactRender = require('fast-react-render');
    
    var element = React.createElement(Component, {property: 'value'});
    console.log(ReactRender.elementToString(element, {context: {}}));