![](https://img.haomeiwen.com/i2340963/873f5e80fcbe2582.jpeg)
为什么服务器端渲染?
服务器端渲染有几个优点:
- 如果http被预先渲染,浏览器的性能就会增加,因为浏览器把所有一起返回,所以浏览器的工作量减少了。另外,HTML页面由CDN缓存,不需要在每一次调用时被重新创建。
- 搜索引擎只能编译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)。
网友评论