React 同构开发(一)

为什么要做同构

要回答这个问题,首先要问什么是同构。所谓同构,顾名思义就是同一套代码,既可以运行在客户端(浏览器),又可以运行在服务器端(node)。

我们知道,在前端的开发过程中,我们一般都会有一个index.html, 在这个文件中写入页面的基本内容(静态内容),然后引入JavaScript脚本根据用户的操作更改页面的内容(数据)。在性能优化方面,通常我们所说的种种优化措施也都是在这个基础之上进行的。在这个模式下,前端所有的工作似乎都被限制在了这一亩三分地之上。

那么同构给了我们什么样的不同呢?前面说到,在同构模式下,客户端的代码也可以运行在服务器上。换句话说,我们在服务器端就可以将不同的数据组装成页面返回给客户端(浏览器)。这给页面的性能,尤其是首屏性能带来了巨大的提升可能。另外,在SEO等方面,同构也提供了极大的便利。除此以外,在整个开发过程中,同构会极大的降低前后端的沟通成本,后端更加专注于业务模型,前端也可以专注于页面开发,中间的数据转换大可以交给node这一层来实现,省去了很多来回沟通的成本。

基于React的同构开发

说了这么多,如何做同构开发呢?
这还得归功于 React提供的服务端渲染。

ReactDOMServer.renderToString  
ReactDOMServer.renderToStaticMarkup

不同于 ReactDom.render将DOM结构渲染到页面, 这两个函数将虚拟DOM在服务端渲染为一段字符串,代表了一段完整的HTML结构,最终以html的形式吐给客户端。

下面看一个简单的例子:

// 定义组件 
import React, { Component, PropTypes } from 'react';

class News extends Component {
	constructor(props) {
		super(props);
	}

	render() {
		var {data} = this.props;
		return <div className="item">
      <a href={data.url}>{ data.title }</a>
    </div>;
	}
}

export default News;

我们在客户端,通常通过如下方式渲染这个组件:

// 中间省略了很多其他内容,例如redux等。
let data = {url: 'http://www.taobao.com', title: 'taobao'}
ReactDom.render(<News data={data} />, document.getElementById("container"));

在这个例子中我们写死了数据,通常情况下,我们需要一个异步请求拉取数据,再将数据通过props传递给News组件。这时候的写法就类似于这样:

Ajax.request({params, success: function(data) {
	ReactDom.render(<News data={data} />, document.getElementById("container"));	
}});

这时候,异步的时间就是用户实际等待的时间。

那么,在同构模式下,我们怎么做呢?

// 假设我们的web服务器使用的是KOA,并且有这样的一个controller  
function* newsListController() {
	
  const data = yield this.getNews({params});

  const data = {
    'data': data
  };
  
  this.body = ReactDOMServer.renderToString(News(data));
};

这样的话,我么在服务端就生成了页面的所有静态内容,直接的效果就是减少了因为首屏数据请求导致的用户的等待时间。除此以外,在禁用JavaScript的浏览器中,我们也可以提供足够的数据内容了。

什么原理

其实,react同构开发并没有上面的例子那么简单。上面的例子只是为了说明服务端渲染与客户端渲染的基本不同点。其实,及时已经在服务端渲染好了页面,我们还是要在客户端重新使用ReactDom.render函数在render一次的。因为所谓的服务端渲染,仅仅是渲染静态的页面内容而已,并不做任何的事件绑定。所有的事件绑定都是在客户端进行的。为了避免客户端重复渲染,React提供了一套checksum的机制。所谓checksum,就是React在服务端渲染的时候,会为组件生成相应的校验和(checksum),这样客户端React在处理同一个组件的时候,会复用服务端已生成的初始DOM,增量更新,这就是data-react-checksum的作用。

所以,最终,我们的同构应该是这个样子的:

// server 端  
function* newsListController() {
	
  const data = yield this.getNews({params});

  const data = {
    'data': data
  };
  let news = ReactDOMServer.renderToString(News(data));
  this.body = '<!doctype html>\n\
                      <html>\
                        <head>\
                            <title>react server render</title>\
                        </head>\
                        <body><div id="container">' +
                            news +
                            '</div><script>var window.__INIT_DATA='+ JSON.stringify(data) +'</script><script src="app.js"></script>\
                        </body>\
                      </html>';
};

// 客户端,app.js中  
let data = JSON.parse(window.__INIT_DATA__);  
ReactDom.render(<News props={data} />, document.getElementById("container"));

小结

最近一直在做同构相关的东西,本文主要讨论react同构开发的基本原理和方式,作为一个引子,其中省去了很多细节问题。关于同构应用开发,其实有很多事情要做,比如node应用的发布、监控、日志管理,react组件是否满足同构要求的自动化检测等。这些事情都是后续要一步一步去做的,到时候也会做一些整理和积累。

posted @ 2016-07-31 23:57  tbingooo  阅读(1813)  评论(3编辑  收藏  举报