React-服务器端渲染

2017-12-13  本文已影响99人  王_建峰
早起

为什么服务器端渲染?

服务器端渲染有几个优点:

  1. 如果http被预先渲染,浏览器的性能就会增加,因为浏览器把所有一起返回,所以浏览器的工作量减少了。另外,HTML页面由CDN缓存,不需要在每一次调用时被重新创建。
  2. 搜索引擎只能编译HTML代码,不能编译JS。目前唯一正确的方式是在服务器端渲染所有搜索引擎相关的信息,不会影响seo。

渲染静态标记

rendrtToStaticMarkup方法接收了一个react组件并将html作为字符串返回。这个可以被一个类似于Handlebars一样的简单引擎模板渲染:

<div>{{{ markup }}}</div>

Reactive组件

组件能被服务器渲染是很好的,但是在大部分情况下是不够的。像事件绑定、属性和状态改变这种完全的交互丢失了。

这些组件只有在React意识到客户端也有组件之后才能被集成。

renderToString方法也把HTML作为string返回,但是与静态变量相比,这个方法还支持客户端交互增量计数器的经典例子:

var Counter = React.createClass({
    getInitialState: function() {
        return {
            count: this.props.initialCount
        };
    },
 
    _increment: function() {
        this.setState({ count: this.state.count + 1 });
    },
 
    render: function() {
        return <span onClick={this._increment}>
            {this.state.count}
        </span>;
    }
});"

这个组件利用如下的例子被服务器渲染:

var React = require('react');
var Counter = React.createFactory(require("./counter"));
var http = require('http');
 
var counterHtml = React.renderToString(
    Counter({ initialCount: 3 })
);
 
http.createServer(function(req, res) {
  if (req.url == '/') {
    res.send('<div id="container">' + counterHtml + '</div>');
  }
}).listen(3000);

如果访问 http://localhost:3000/,浏览器会展示初始数字3.

单击一个span元素,假设会触发一个click事件,什么都不会发生。这里有个很简单的解释:React不知道客户端的这个组件,因此不能够绑定处理事件或者执行再次渲染。

为了响应单击事件,这个组件必须被浏览器再创造。

var Counter = React.createFactory(require("./counter"));

React.render(Counter({ initialCount: 3 }), document.getElementById("container")

例如,我们假设React.js和组件都已经被浏览器加载。

这里例子包含了一点魔法:如果被props属性加载的组件跟服务器端的相同,就不再次渲染。React识别到DOM还没有被改变,但是将来可能改变,为了绑定事件React执行所有必要的步骤。

如果组件不被重新渲染,会对性会能有一定的提升。

Synchronizing props(同步props 属性)

属性背后的原理很简单:客户端需要将props传递给服务器

// server.js
// ...
var props = { initialCount: 3 };
var counterHtml = React.renderToString(
    Counter(props)
);

  res.send(
      '<div id="container">' + counterHtml + '</div>' +
      '<script>' +
        'var Counter = React.createFactory(require("./counter"));' +
        'React.render(Counter(' + safeStringify(props) + '), document.getElementById("container"))' +
      '</script>'
  );"

Note:safeStringify方法可以把JSON安全的嵌入JavaScript标签中。
在第五行这个props{ initialCount: 3 }被传递到服务端组件。在第十二行被传递到客户端。

props也可以被放进一个分开的script标签中

<script id="props" type="application/json">
    {{{ theProps }}}
</script>
<script>
    var props = JSON.parse(document.getElementById("props").innerHTML);
    // ...
</script>

因为第二个script标签现在是完全独立的,可以直接放进counter.jsx中

if (typeof window !== 'undefined') {
    var props = JSON.parse(document.getElementById("props").innerHTML);
    React.render(Counter(props), document.getElementById("container"));
}

更进一步,我们可以把props直接放进组件的渲染方法中:

render: function() {
    var json = safeStringify(this.props);
    var propStore = <script type="application/json"
        id="someId"
        dangerouslySetInnerHTML={{__html: json}}>
    </script>;
 
    return <div onClick={this._increment}>
        {propStore}
        {this.state.count}
    </div>;
}

把props放进渲染方法中不是特别好看,但是优点是负责服务端渲染的所有代码都在React组件中。

组件进入浏览器

除了React,浏览器还需要了解React组件。为了不加载每一个组件,像 Browserify 这样的离散工具创建了完全绑定。我们去看看这个例子(非常基本的)

http.createServer(function(req, res) {
  if (req.url == '/') {
    // ...
  } else if (req.url == '/bundle.js') {
    res.setHeader('Content-Type', 'text/javascript')
    browserify()
      .require('./counter.js', {expose: 'counter'})
      .transform({global: true}, literalify.configure({react: 'window.React'}))
      .bundle()
      .pipe(res)
  }

React本质上是怎么同步参数的?

组件可以通过服务器上的一个包含data-react-checksum属性的renderToString被渲染

<div data-reactid=".pxv0hfgr28" data-react-checksum="85249504">
  4
</div>

进去React源代码看看,(ReactServerRendering.js)展示了后台是怎么运行的:

function renderToString(component) {
    ...
    return transaction.perform(function() {
      var componentInstance = instantiateReactComponent(element, null);
      var markup = componentInstance.mountComponent(id, transaction, emptyObject);
      return ReactMarkupChecksum.addChecksumToMarkup(markup);
    }, null);

addChecksumToMarkup方法创建了一个HTML标记的Adler-32 Checksum组件,并把它附在在服务器端被渲染的组件上。

如果这个组件在随后在客户端被渲染,canReuseMarkup(ReactMarkupChecksum.js)这个方法可以为re-rendering做功能测试

canReuseMarkup: function(markup, element) {
    var existingChecksum = element.getAttribute(
        ReactMarkupChecksum.CHECKSUM_ATTR_NAME
    );
    existingChecksum = existingChecksum && parseInt(existingChecksum, 10);
    var markupChecksum = adler32(markup);
    return markupChecksum === existingChecksum;
}

结论:

这个例子仅仅展示了是怎么工作的,没有必要必须走这一步
有很多优雅的方法同步客户端和服务器端,像fluxible-app(dehydration/rehydration)。

上一篇 下一篇

猜你喜欢

热点阅读