【发布时间】:2016-04-16 04:48:34
【问题描述】:
我注意到在服务器上渲染大型组件树时,reactDOM.renderToString() 方法开始显着变慢。
背景
一点背景。该系统是一个完全同构的堆栈。最高级别的App 组件渲染模板、页面、dom 元素和更多组件。查看 react 代码,我发现它渲染了大约 1500 个组件(这包括任何被视为简单组件的简单 dom 标记,<p>this is a react component</p>。
在开发中,渲染约 1500 个组件需要约 200-300 毫秒。通过删除一些组件,我能够在约 175-225 毫秒内获得约 1200 个组件。
在生产中,大约 1500 个组件上的 renderToString 大约需要大约 50-200 毫秒。
时间似乎是线性的。没有一个组件是慢的,而是许多组件的总和。
问题
这会在服务器上产生一些问题。冗长的方法导致服务器响应时间长。 TTFB 远高于应有的水平。对于 api 调用和业务逻辑,响应应该是 250 毫秒,但是对于 250 毫秒的 renderToString,它会加倍!对 SEO 和用户不利。另外,作为同步方法,renderToString() 可以阻塞节点服务器并备份后续请求(这可以通过使用 2 个单独的节点服务器来解决:1 个作为 Web 服务器,1 个作为服务单独渲染 react)。
尝试
理想情况下,在生产环境中 renderToString 需要 5-50 毫秒。我一直在研究一些想法,但我不确定最好的方法是什么。
想法一:缓存组件
任何标记为“静态”的组件都可以被缓存。通过使用渲染标记保存缓存,renderToString() 可以在渲染之前检查缓存。如果它找到一个组件,它会自动抓取字符串。在高级组件上执行此操作将保存所有嵌套子组件的安装。您必须将缓存的组件标记的 react rootID 替换为当前的 rootID。
想法 2:将组件标记为简单/愚蠢
通过将组件定义为“简单”,react 应该能够在渲染时跳过所有生命周期方法。 React 已经为核心的 react dom 组件(<p/>、<h1/> 等)做到了这一点。扩展自定义组件以使用相同的优化会很好。
想法 3:在服务器端渲染时跳过组件
服务器不需要返回的组件(没有 SEO 值)可以简单地在服务器上跳过。客户端加载后,将 clientLoaded 标志设置为 true 并将其传递下来以强制重新渲染。
关闭和其他尝试
到目前为止,我实施的唯一解决方案是减少在服务器上呈现的组件数量。
我们正在关注的一些项目包括:
- React-dom-stream(仍在努力实现这个以进行测试)
- Babel inline elements (似乎这与想法 2 一致)
有人遇到过类似的问题吗?你能做什么? 谢谢。
【问题讨论】:
-
这是一个有趣的问题。您是否遇到过需要解决的性能问题?如果是这样,您是否能够将问题定位到特定的子树?
-
对于我的一些更复杂的页面,有 1500 多个组件正在呈现(因为 react 将所有内容分解为一个“组件”)。任何有大约 1000 多个组件的地方似乎都非常慢。我用 ReactCompositeComponent 弄乱了反应源代码,如果它们符合某些条件,则将它们添加到缓存中。但是,这会导致从缓存中提取时不变量,因为每个组件都有不正确的根反应 ID。但是,如果我从缓存中提取结果字符串而不是安装它,它确实大大提高了性能;)
-
我假设“组件”是指已定义的组件,而不是内置的 jsx 标签。如果包括这些,那么 1500 并非完全不合理。但无论如何,我仍在努力寻找问题的根源。是数据层慢还是计算层慢?如果所有数据都在内存中,还会出现问题吗?
-
我没有缓存问题的答案,但我希望这里有人知道。同时,我会尝试找出哪些树存在问题并尝试优化这些树(例如,尽可能使用纯的、无状态的组件来避免额外的生命周期方法等等)。对于特别大的页面,我还会考虑仅预渲染关键路径,然后在客户端加载其余路径。这可能会影响 SEO,但可能不会影响 Google。您可以为自我识别的爬虫预渲染更多内容。
-
回复。想法 2,React 已经支持stateless functional components。它们只有一个渲染方法,没有状态,也绝对没有生命周期方法。我怀疑 react 是否对这些组件进行了记忆,但将来可能会改变。
标签: performance reactjs isomorphic-javascript render-to-string react-dom