RAX 服务端渲染性能优化揭秘

564 阅读3分钟

前言

在我们日常用 React / Vue 等开发 SPA 的时候总会遇到需要提升首屏渲染速度的需求,在这时候我们通常会想到使用 SSR 来进行优化,如果想再进一步提升速度呢?大多数都会从 接口响应速度 首屏渲染的数据量 等方面进行优化。但我们换个思路,难道不能从第一步 renderToString 的时候去优化吗?Rax 给我们提供了一个很好的思路。

Rax 是什么?

套用官网的介绍:超轻量,高性能,易上手的前端解决方案。一次开发多端运行,解放重复工作,专注产品逻辑,提升开发效率。

简单来说就是基于 React 多端构建的框架,本文重点不是介绍 Rax 这个框架所以就不展开了,有兴趣的可以去官网看看。
这里重点介绍一下 Rax 号称采用 静态模板 + 动态组件的混合渲染模式,性能遥遥领先同类框架 到底是如何实现的。

SSR 服务端渲染

服务端渲染是什么我就不作过多的介绍了,如果现在还不了解,社区里有很多很详细的文章我建议先阅读一下。
接下来我们先看一组数据,这是官方使用 benchmark 各框架进行的服务端渲染性能测试,可以看到 Rax 的 SSR 性能确实比 React 要领先不少,Rax 到底如何做到的?我们接着往下看。

React(16.12.0)#renderToString x 1,178 ops/sec ±1.23% (85 runs sampled)
Rax(1.1.1)#renderToString x 6,047 ops/sec ±1.73% (82 runs sampled)
Inferno(7.3.3)#renderToString x 3,335 ops/sec ±1.77% (82 runs sampled)
Preact(10.2.1)#renderToString x 1,005 ops/sec ±1.10% (86 runs sampled)
Marko(4.18.33)#renderToString x 10,291 ops/sec ±1.64% (86 runs sampled)
xtemplate(4.7.2)#render x 20,600 ops/sec ±2.89% (84 runs sampled)

The benchmark was run on:
   PLATFORM: linux 5.0.0-1027-azure
   CPU: Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz
   SYSTEM MEMORY: 6.782737731933594GB
   NODE VERSION: v10.18.1

源码分析

首先,先了解一下 react-dom 提供的 renderToString 方法是怎么把你的组件变成一个 html 字符串的。 通过阅读其源码就会发现,其他逻辑非常简单,拿到数据后不断地遍历你的组件对象,一层层的组合字符串,最终等到一个完整的 html 字符串
至于 Rax,阅读了他的源码后发现他 raxRenderToString 方法核心其实跟 React 的相差不大,都是递归查找组件,关键的点在于 webpack 编译 的阶段,在编译时增加了一个 Rax 提供的插件 babel-plugin-transform-jsx-to-html ,他的作用就是把 组件里静态内容在 webpack 构建的时候预先编译处理好 有兴趣可以阅读一下源码,下面我们直接举个例子来说明:

案例分析

export default class App extends React.Component {
  state = {};

  render() {
    const props = this.props || {};

    return (
      <div className="root" onClick={this.state.onClick}>
        <Banner cacheKey={"banner"} data={props.bannerData} />
        <List cacheKey={"list"} data={props.listData} />
      </div>
    );

  }
};

React

根据上面的的组件,编译后通过 React.createElement 得到的对象如下,这就是一个标准的 React 组件对象,然后通过 react-dom 提供的 renderToString 即可得倒一个完整的 html 字符串。

{
   '$$typeof': Symbol(react.element),
   type: 'div',
   key: null,
   ref: null,
   props: { 
     className: 'root',
     onClick: undefined,
     children: [ [Object], [Object] ]
   },
  _owner: null 
 }

Rax

根据上面的的组件,编译后通过 React.createElement 得倒的对如下,这样就很能很清晰的看到两者的差别在哪了, Rax 通过在编辑阶段,预先把静态的模版数据编译好,在实际 SSR 渲染的时候就能省下这些时间从而提升其性能

[ 
  { __html: '<div class="root"' },
  { __attrs: { onClick: undefined } },
  { __html: '>' },
  { 
    type: [Function: r],
    key: null,
    ref: null,
    props: { data: [Array] },
    _owner: null
  },
  {
    type: [Function: r],
    key: null,
    ref: null,
    props: { data: [Array] },
    _owner: null
  },
  { __html: '</div>' } 
]

下面用一张图来总结两个 SSR 方案的差异,希望看完后对你有所启发,写得不好的地方还望指出,不吝赐教。
image.png