React-服务器端渲染

早起

为什么服务器端渲染?

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

  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)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容