React 同构思想
作者:yangchunwen
React比较吸引我的地方在于其客户端-服务端同构特性,服务端-客户端可复用组件,本文来简单介绍下这一架构思想。
出于篇幅原因,本文不会介绍React基础,所以,如果你还不清楚React的state/props/生存周期等基本概念,建议先学习相关文档
客户端React
先来回顾一下React如何写一个组件。比如要做一个下面的表格:
可以这样写:
先创建一个表格类。
Table.js
var React = require('react');
var DOM = React.DOM;
var table = DOM.table, tr = DOM.tr, td = DOM.td;
module.exports = React.createClass({
render: function () {
return table({
children: this.props.datas.map(function (data) {
return tr(null,
td(null, data.name),
td(null, data.age),
td(null, data.gender)
);
})
});
}
});
假设已经有了我们要的表格的结构化数据。
datas.js:
// 三行数据,分别包括名字、年龄、性别
module.exports = [
{
'name': 'foo',
'age': 23,
'gender': 'male'
},
{
'name': 'bar',
'age': 25,
'gender': 'female'
},
{
'name': 'alice',
'age': 34,
'gender': 'male'
}
];
有了表格类和相应的数据之后,就可以调用并渲染这个表格了。
render-client.js
var React = require('react');
var ReactDOM = require('react-dom');
// table类
var Table = require('./Table');
// table实例
var table = React.createFactory(Table);
// 数据源
var datas = require('./datas');
// render方法把react实例渲染到页面中 https://facebook.github.io/react/docs/top-level-api.html#reactdom
ReactDOM.render(
table({datas: datas}),
document.body
);
我们把React基础库
、Table.js
、datas.js
、render-client.js
等打包成pack.js
,引用到页面中:
<!doctype html>
<html>
<head>
<title>react</title>
</head>
<body>
</body>
<script src="pack.js"></script>
</html>'
这样页面便可按数据结构渲染出一个表格来
这里 pack.js 的具体打包工具可以是grunt/gulp/webpack/browerify等,打包方法不在这里赘述
这个例子的关键点是使用props
来传递单向数据流。例如,通过遍历从``props传来的数据
datas```生成表格的每一行数据:
this.props.datas.map...
组件的每一次变更(比如有新增数据),都会调用组件内部的render方法,更改其DOM结构。上面这个例子中,当给datas
push新数据时,react会自动为页面中的表格新增数据行。
服务端React
上面的例子中创建的Table
组件,出于性能、SEO等因素考虑,我们会考虑在服务端直接生成HTML结构,这样就可以在浏览器端直接渲染DOM了。
这时候,我们的Table
组件,就可以同时在客户端和服务端使用了。
只不过与浏览器端使用ReactDOM.render
指定组件的渲染目标不同,在服务器中渲染,使用的是ReactDOMServer这个模块,它有两个生成HTML字符串的方法:
关于这两个方法的区别,我想放到后面再来解释,因为跟后面介绍的内容很有关系。
有了这两个方法,我们来创建一个在服务端nodejs环境运行的文件,使之可以直接在服务端生成表格的HTML结构。
render-server.js:
var React = require('react');
// 与客户端require('react-dom')略有不同
var React = require('react');
// 与客户端require('react-dom')略有不同
var ReactDOMServer = require('react-dom/server');
// table类
var Table = require('./Table');
// table实例
var table = React.createFactory(Table);
module.exports = function () {
return ReactDOMServer.renderToString(table(datas));
};
上面这段代码复用了同一个Table
组件,生成浏览器可以直接渲染的HTML结构,下面我们通过改改nodejs的官方Hello World来做一个真实的页面。
server.js :
var makeTable = require('./render-server');
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
var table = makeTable();
var html = '<!doctype html>\n\
<html>\
<head>\
<title>react server render</title>\
</head>\
<body>' +
table +
'</body>\
</html>';
res.end(html);
}).listen(1337, "127.0.0.1");
console.log('Server running at http://127.0.0.1:1337/');
这时候运行node server.js
就能看到,不实用js,达到了同样的表格效果,这里我使用了同一个Table.js
,完成客户端及服务端的同构,一份代码,两处使用。
这里我们通过查看页面的HTML源码,发现表格的DOM中带了一些数据:
data-reactid
/ data-react-checksum
都是些啥?这里同样先留点悬念,后面再解释。
服务端 + 客户端渲染
上面的这个例子,通过在服务端调用同一个React组件,达到了同样的界面效果,但是有人可能会不开心了:貌似有点弱啊!
上面的例子有两个明显的问题:
datas.js 数据源是写死的,不符合大部分真实生产环境
服务端生成HTML结构有时候并不完善,有时候不借助js是不行的。比如当我们的表格需要轮询服务器的数据接口,实现表格数据与服务器同步的时候,怎么实现一个组件两端使用。
为了解决这个问题,我们的Table组件需要变得更复杂。
数据源
假设我们的表格数据每过一段时间要和服务端同步,在浏览器端,我们必须借助ajax
,React官方给我们指明了这类需求的方向,通过componentDidMount
这一生存周期方法来拉取数据。
componentDidMount
方法,我个人把它比喻成一个“善后”的方法,就是在React把基本的HTML结构挂载到DOM中后,再通过它来做一些善后的事情,例如拉取数据更新DOM等等。
于是我们改一下我们的``Table组件,去掉假数据
datas.js,在
componentDidMount```中调用我们封装好的抓取数据方法,每三秒去服务器抓取一次数据并更新到页面中。
Table.js:
var React = require('react');
var ReactDOM = require('react-dom');
var DOM = React.DOM;
var table = DOM.table, tr = DOM.tr, td = DOM.td;
var Data = require('./data');
module.exports = React.createClass({
render: function () {
return table({
children: this.props.datas.map(function (data) {
return tr(null,
td(null, data.name),
td(null, data.age),
td(null, data.gender)
);
})
});
},
componentDidMount: function () {
setInterval(function () {
Data.fetch('http://datas.url.com').then(function (datas) {
this.setProps({
datas: datas
});
});
}, 3000)
}
});
这里假设我们已经封装了一个拉取数据的
Data.fetch
方法,例如Data.fetch = jQuery.ajax
到这一步,我们实现了客户端的每3秒自动更新表格数据。那么上面这个Table组件是不是可以直接复用到服务端,实现数据拉取呢,不好意思,答案是“不”。
React的奇葩之一,就是其组件有“生存周期”这一说法,在组件的生命的不同时期,例如异步数据更新,DOM销毁等等过程,都会调用不同的生命周期方法。
然而服务端情况不同,对服务端来说,它要做的事情便是:去数据库拉取数据 -> 根据数据生成HTML -> 吐给客户端。这是一个固定的过程,拉取数据和生成HTML过程是不可打乱顺序的,不存在先把内容吐给客户端,再拉取数据这样的异步过程。
所以,componentDidMount
这样的“善后”方法,React在服务器渲染组件的时候,就不适用了。
而且我还要告诉你,componentDidMount
这个方法,在服务端确实永远都不会执行!
看到这里,你可能要想,这步坑爹吗!搞了半天,这个东西只能在客户端用,说好的同构呢!
别急,拉取数据,我们需要另外的方法。
React中可以通过statics
定义“静态方法”,学过面向对象编程的同学,自然懂statics
方法的意思,没学过的,拉出去打三十大板。
我们再来改一下Table
组件,把拉取数据的Data.fetch
逻辑放到这里来。
Table.js:
var React = require('react');
var DOM = React.DOM;
var table = DOM.table, tr = DOM.tr, td = DOM.td;
var Data = require('./data');
module.exports = React.createClass({
statics: {
fetchData: function (callback) {
Data.fetch().then(function (datas) {
callback.call(null, datas);
});
}
},
render: function () {
return table({
children: this.props.datas.map(function (data) {
return tr(null,
td(null, data.name),
td(null, data.age),
td(null, data.gender)
);
})
});
},
componentDidMount: function () {
setInterval(function () {
// 组件内部调用statics方法时,使用this.constructor.xxx...
this.constructor.fetchData(function (datas) {
this.setProps({
datas: datas
});
});
}, 3000);
}
});
非常重要:Table组件能在客户端和服务端复用fetchData方法拉取数据的关键在于,
Data.fetch
必须在客户端和服务端有不同的实现!例如在客户端调用Data.fetch
时,是发起ajax请求,而在服务端调用Data.fetch
时,有可能是通过UDP协议从其他数据服务器获取数据、查询数据库等实现
由于服务端React不会调用componentDidMount
,需要改一下服务端渲染的文件,同样不再通过datas.js获取数据,而是调用Table的静态方法fetchData
,获取数据后,再传递给服务端渲染方法renderToString
,获取数据在实际生产环境中是个异步过程,所以我们的代码也需要是异步的:
render-server.js:
var React = require('react');
var ReactDOMServer = require('react-dom/server');
// table类
var Table = require('./Table');
// table实例
var table = React.createFactory(Table);
module.exports = function (callback) {
Table.fetchData(function (datas) {
var html = ReactDOMServer.renderToString(table({datas: datas}));
callback.call(null, html);
});
};
这时候,我们的Table
组件已经实现了每3秒更新一次数据,所以,我们既需要在服务端调用React初始html数据,还需要在客户端调用React实时更新,所以需要在页面中引入我们打包后的js。
server.js
var makeTable = require('./render-server');
var http = require('http');
http.createServer(function (req, res) {
if (req.url === '/') {
res.writeHead(200, {'Content-Type': 'text/html'});
makeTable(function (table) {
var html = '<!doctype html>\n\
<html>\
<head>\
<title>react server render</title>\
</head>\
<body>' +
table +
'<script src="pack.js"></script>\
</body>\
</html>';
res.end(html);
});
} else {
res.statusCode = 404;
res.end();
}
}).listen(1337, "127.0.0.1");
console.log('Server running at http://127.0.0.1:1337/');
成果
通过上面的改动,我们在服务端获取表格数据,生成HTML供浏览器直接渲染;页面渲染后,Table组件每隔3秒会通过ajax获取新的表格数据,有数据更新的话,会直接更新到页面DOM中。
checksum的作用
还记得前面的问题么?
ReactDOMServer.renderToString
和 ReactDOMServer.renderToStaticMarkup
有什么不同?服务端生成的data-react-checksum
是干嘛使的?
我们想一想,就算服务端没有初始化HTML数据,仅仅依靠客户端的React也完全可以实现渲染我们的表格,那服务端生成了HTML数据,会不会在客户端React执行的时候被重新渲染呢?我们服务端辛辛苦苦生成的东西,被客户端无情地覆盖了?
当然不会!React在服务端渲染的时候,会为组件生成相应的校验和(checksum),这样客户端React在处理同一个组件的时候,会复用服务端已生成的初始DOM,增量更新,这就是data-react-checksum
的作用。
ReactDOMServer.renderToString
和 ReactDOMServer.renderToStaticMarkup
的区别在这个时候就很好解释了,前者会为组件生成checksum,而后者不会,后者仅仅生成HTML结构数据。
所以,只有你不想在客户端-服务端同时操作同一个组件的时候,方可使用renderToStaticMarkup
。
React 同构思想的更多相关文章
- [转] React同构思想
React比较吸引我的地方在于其客户端-服务端同构特性,服务端-客户端可复用组件,本文来简单介绍下这一架构思想. 出于篇幅原因,本文不会介绍React基础,所以,如果你还不清楚React的state/ ...
- React同构直出优化总结
收录待用,修改转载已取得腾讯云授权 作者:郭林烁 joeyguo 原文地址 React 的实践从去年在 PC QQ家校群开始,由于 PC 上的网络及环境都相当好,所以在使用时可谓一帆风顺,偶尔遇到点小 ...
- React同构直出原理浅析
通常,当客户端请求一个包含React组件页面的时候,服务端首先响应输出这个页面,客户端和服务端有了第一次交互.然后,如果加载组件的过程需要向服务端发出Ajax请求等,客户端和服务端又进行了一次交互,这 ...
- React 同构
React 同构 搬运 https://segmentfault.com/a/1190000004671209 究竟什么是同构呢? 同构就是希望前端 后端都使用同一套逻辑 同一套代码 Nodejs出现 ...
- React 设计思想
https://github.com/react-guide/react-basic React 设计思想 译者序:本文是 React 核心开发者.有 React API 终结者之称的 Sebasti ...
- 打造高可靠与高性能的React同构解决方案
前言 随着React的兴起, 结合Node直出的性能优势和React的组件化,React同构已然成为趋势之一.享受技术福利的同时,直面技术挑战,在复杂场景下,挑战10倍以上极致的性能优化. 什么是同构 ...
- React 同构开发(二)
React 同构 所谓同构,简单的说就是客户端的代码可以在服务端运行,好处就是能极大的提升首屏时间,避免白屏,另外同构也给SEO提供了很多便利. React 同构得益于 React 的虚拟 DOM.虚 ...
- React 同构开发(一)
为什么要做同构 要回答这个问题,首先要问什么是同构.所谓同构,顾名思义就是同一套代码,既可以运行在客户端(浏览器),又可以运行在服务器端(node). 我们知道,在前端的开发过程中,我们一般都会有一个 ...
- 自制的React同构脚手架
代码地址如下:http://www.demodashi.com/demo/12575.html Web前端世界日新月异变化太快,为了让自己跟上节奏不掉队,总结出了自己的一套React脚手架,方便日后新 ...
随机推荐
- JAVA编程思想读书笔记(五)--多线程
接上篇JAVA编程思想读书笔记(四)--对象的克隆 No1: daemon Thread(守护线程) 参考http://blog.csdn.net/pony_maggie/article/detail ...
- 洛谷P4768 [NOI2018]归程 [可持久化并查集,Dijkstra]
题目传送门 归程 格式难调,题面就不放了. 分析: 之前同步赛的时候反正就一脸懵逼,然后场场暴力大战,现在呢,还是不会$Kruskal$重构树,于是就拿可持久化并查集做. 但是之前做可持久化并查集的时 ...
- Django+Nginx+uwsgi搭建自己的博客(一)
最近对写爬虫有些厌倦了,于是将方向转移到了Web开发上.其实在之前自己也看过一部分Flask的资料,但总觉得Flask的资料有些零散,而且需要的各种扩展也非常多.因此,我将研究方向转移到了另一个主流的 ...
- BZOJ2434 NOI2011阿狸的打字机
询问x这个串在y中出现的次数. fail数组有一个性质就是一旦a的fail指向b那么b所代表的字串一定是a的后缀. 所以我们看fail树(即按fail反向建树)中x的子树有多少y的结点即可. 这个操作 ...
- 数位dp小结以及模板
这里是网址 别人的高一啊QAQ.... 嗯一般记忆化搜索是比递推好写的所以我写的都是dfs嗯......(因为我找不到规律啊摔,还是太菜.....) 显然这个东西的条件是非常的有套路..但是不管怎么样 ...
- HihoCoder - 1756 打怪
题面在这里! 拆成两个部分分别算显然比较简单. 前面一个部分排个序枚举最大值算就好啦. 后面的就相当于把每一种数值的贡献加起来,也可以在排完序之后的a[]上面直接算出来. #include<bi ...
- 腾讯通消息webSDK踩坑
1.腾讯通提供一个通过http协议的接口,可用于发送消息,公告等功能,要使用其功能首先要开启RTX_HTTPServer服务. 2.阅读文档http://rtx.tencent.com/sdk/,为了 ...
- Codeforces Round #299 (Div. 2) B. Tavas and SaDDas 水题
B. Tavas and SaDDas Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/535/p ...
- zoj 1610 Count the Colors 线段树区间更新/暴力
Count the Colors Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://acm.zju.edu.cn/onlinejudge/show ...
- HDU 4669 Mutiples on a circle (2013多校7 1004题)
Mutiples on a circle Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 65535/65535 K (Java/Oth ...