React Server Render

前端之家收集整理的这篇文章主要介绍了React Server Render前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

概述

一直想用React做些东西,苦于没有实际项目练手,所以一直都是自己在搞些小玩意儿,做过用React Router构建的内部订餐系统,是个SPA,也在社区分享过。由于一个人做全栈开发,数据库(mongodb)全靠自己设,需求全靠自己编,页面全靠自己扯,心好累,感觉不会在爱了!
SPA用来构建内部的系统完全没问题,但是用来做门户、做电商网站就不行了,为啥?因为SEO,很多的MVVM,MV*框架不能用、不敢用都是基于这个原因(当然也可能因为我不会用)。
最近拿CNode的API做了个React服务器端渲染的例子,这里跟大家分享下这个项目的构建过程和代码组织,未必好,主要提供一个思路。

搭建


整体项目目录如上,这里作个说明,附上代码地址,上面有说明怎么使用。

  • component 我们的组件目录,这里放置了view、ui等组件

  • lib 后端代码,如过滤器等

  • node_modules 依赖包

  • public 静态资源

  • routes 路由

浏览器端和服务器端的代码我们没必要完全独立,实际上有时候代码是可以复用的。举个例子
表单异步提交的时候,后端返回一个state状态告知是否成功,相信大部分的人的第一反应都是抽出常量
constants.js

  1. module.exports = {
  2. state: {
  3. SUCCESS: 10000
  4. }
  5. };

当然了,浏览器端也是要判断这个state的,为了提高代码的复用性,这里同样抽出
constants.js

  1. module.exports = {
  2. state: {
  3. SUCCESS: 10000
  4. }
  5. };

虽然内容相同,实际上这是两个不同的js,分处不同的目录,oh shit。我的开发理念一般是这样的

相同的代码坚决不写第二遍,特殊情况除外!

采用React后端渲染,我用了webpack打包,实际上就避免了这个问题,写一份constants.js,打包到浏览器端去,NICE!

编码

既然是后端渲染,首先得选择一个模板引擎,这里我采用的| 90ee772881e409df0a8a3bb9717d59483 |,具体配置和使用可以参考文档,这里我就不赘述了。既然是构建SPA必不可少得要个路由管理,这里我选择的react-router,react-engine也是兼容react-router的,真棒!拿首页的编码举个例子

route

路由我这里用的自己的路由组织express-mapping,看首页代码
routes/index.js

  1. var constants = require('../lib/constants');
  2. var request = require('superagent');
  3. var queryString = require('query-string');
  4.  
  5. module.exports = {
  6. get: {
  7. '/': function (req,res) {
  8. request
  9. .get('http://cnodejs.org/api/v1/topics?' + queryString.stringify(req.query))
  10. .end(function (err,response) {
  11. if (err) {
  12. throw err;
  13. }
  14. res.render(req.url,{
  15. state: constants.state.SUCCESS,data: response.body.data,title: 'CNode:Node.js专业中文社区'
  16. });
  17.  
  18. });
  19. }
  20. }
  21. };

实际上,res.render方法被我重写了,根据发的请求是不是ajax返回不同的内容
lib/filter.js

  1. /**
  2. * 区分ajax请求与普通请求
  3. */
  4. req.isXmlHttpRequest = (function () {
  5. var xRequestedWith = req.headers['x-requested-with'];
  6. return xRequestedWith && xRequestedWith.toLowerCase() === 'xmlhttprequest';
  7. })();
  8.  
  9. /**
  10. * 重写res.render方法
  11. */
  12. var render = res.render;
  13.  
  14. res.render = function (view,data) {
  15. var response = _.extend({session: req.session},data);
  16. req.isXmlHttpRequest ? res.json(response) : render.call(res,view,response);
  17. };

这样我们又做到了接口的复用!

组件

来看看我们打包的入口

component/index.js

  1. var React = require('react');
  2. var Router = require('react-router');
  3. var $ = require('jquery');
  4. var Routes = require('./routes.jsx');
  5.  
  6. var CLIENT_VARIABLENAME = '__REACT_ENGINE__';
  7.  
  8. var _window;
  9. var _document;
  10. if (typeof window !== 'undefined' && typeof document !== 'undefined') {
  11. _window = window;
  12. _document = document;
  13. }
  14.  
  15. document.addEventListener('DOMContentLoaded',function onLoad() {
  16. Router.run(Routes,Router.HistoryLocation,function onRouterRun(Root,state) {
  17. var props = _window[CLIENT_VARIABLENAME];
  18. if (props) {
  19. var componentInstance = React.createElement(Root,props);
  20. React.render(componentInstance,_document);
  21. _window[CLIENT_VARIABLENAME] = null;
  22. } else {
  23. $.get(state.path).then(function (data) {
  24. var componentInstance = React.createElement(Root,data);
  25. React.render(componentInstance,_document);
  26. });
  27. }
  28.  
  29. });
  30. });

后端渲染的原理是这样的,当我们第一访问的时候,node端返回React渲染好的HTML结构,并通过script标签将数据传递到前端,然后在浏览器端获取到传递的数据再渲染一次,总共渲染了两次。当我们在浏览器端进行切换切换的时候,页面是不刷新的,通过ajax请求获取到数据,重新渲染DOM结构。

component/routes.jsx

再来看看路由,不熟悉React Router的最好熟悉下,会用到

  1. var React = require('react');
  2. var Router = require('react-router');
  3.  
  4. var Route = Router.Route;
  5. var DefaultRoute = Router.DefaultRoute;
  6.  
  7. var App = require('./app.jsx');
  8. var Index = require('./views/index.jsx');
  9.  
  10. var TopicDetail = require('./views/topic/detail.jsx');
  11. var UserDetail = require('./views/user/detail.jsx');
  12.  
  13. var routes = (
  14. <Route handler={App} path="/">
  15. <DefaultRoute name="index" handler={Index}/>
  16. <Route name="topic-detail" path="topic/:topicId" handler={TopicDetail}/>
  17. <Route name="user-detail" path="user/:loginname" handler={UserDetail}/>
  18. </Route>
  19. );
  20.  
  21. module.exports = routes;

都是些基本的路由配置

component/app.jsx

再来看下入口组件

  1. var React = require('react');
  2. var Router = require('react-router');
  3.  
  4. var Layout = require('./views/layouts/default.jsx');
  5.  
  6. var RouteHandler = Router.RouteHandler;
  7.  
  8.  
  9. module.exports = React.createClass({
  10. render: function () {
  11. var data = this.props.data;
  12. return (
  13. <Layout title={this.props.title}>
  14. <RouteHandler data={data}/>
  15. </Layout>
  16. )
  17. }
  18. });

Layout就是我们的布局了,相同的代码总要抽出来的。

  1. var React = require('react');
  2. var constants=require('../../../lib/constants');
  3.  
  4. var Footer=require('../partials/footer.jsx');
  5.  
  6. module.exports = React.createClass({
  7. render: function render() {
  8. return (
  9. <html>
  10. <head>
  11. <title>{this.props.title}</title>
  12. <Meta charSet='utf-8'/>
  13. <Meta name="keywords" content={constants.promotion.keywords}/>
  14. <Meta name="description" content={constants.promotion.description}/>
  15. <link rel="icon" href="//dn-cnodestatic.qBox.me/public/images/cnode_icon_32.png" type="image/x-icon"/>
  16. <link rel="stylesheet" href="/css/font-awesome.min.css"/>
  17. <link rel="stylesheet" href="/css/bootstrap.css"/>
  18. <link rel="stylesheet" href="/css/style.css"/>
  19. </head>
  20. <body>
  21. {this.props.children}
  22. <Footer />
  23. <script src="/build/vendor.js"></script>
  24. <script src="/build/bundle.js"></script>
  25. </body>
  26. </html>
  27. );
  28. }
  29. });

component/views/index.jsx

这里就是业务代码

  1. var React = require('react');
  2. var Router = require('react-router');
  3. var $ = require('jquery');
  4. var Navbar = require('./partials/navbar.jsx');
  5. var queryString = require('query-string');
  6. var utils=require('../component/utils');
  7.  
  8. var Link = Router.Link;
  9.  
  10.  
  11. var Label = React.createClass({
  12. render: function () {
  13. var tab = this.props.tab;
  14. var data = this.props.data;
  15.  
  16. if (data.top) {
  17. return <label className="label label-success">置顶</label>;
  18. }
  19.  
  20. if (data.good) {
  21. return <label className="label label-success">精华</label>;
  22. }
  23.  
  24. if (!tab || tab === 'all') {
  25. if (data.tab === 'share') {
  26. return <label className="label label-default">分享</label>;
  27. }
  28.  
  29. if (data.tab === 'ask') {
  30. return <label className="label label-default">问答</label>;
  31. }
  32.  
  33. if (data.tab === 'job') {
  34. return <label className="label label-default">招聘</label>;
  35. }
  36. }
  37.  
  38. return null;
  39. }
  40. });
  41.  
  42. module.exports = React.createClass({
  43. getInitialState: function () {
  44. return {
  45. data: this.props.data || [],page: 1
  46. }
  47. },componentWillReceiveProps: function (nextProps) {
  48. this.setState({
  49. data: nextProps.data,page: 1
  50. });
  51. },componentDidMount: function () {
  52. var loading = false;
  53. $(window).on('scroll',function () {
  54. var fromBottom = $(document).height() - $(window).height() - $(window).scrollTop();
  55.  
  56. if (fromBottom <= 10 && !loading) {
  57. loading = true;
  58. var query = queryString.parse(location.search);
  59. query.page = this.state.page + 1;
  60. $.get(location.pathname + '?' + queryString.stringify(query),function (response) {
  61. this.setState({
  62. data: this.state.data.concat(response.data),page: this.state.page + 1
  63. },function () {
  64. loading = false;
  65. });
  66. }.bind(this));
  67. }
  68. }.bind(this));
  69. },render: function () {
  70. var tab = this.props.query.tab;
  71. return (
  72. <div className="index">
  73. <Navbar />
  74.  
  75. <div className="container">
  76. <ul className="nav nav-tabs">
  77. <li className={!tab || tab==='all'?'active':''}>
  78. <Link to="index" query={{tab:'all'}}>全部</Link>
  79. </li>
  80. <li className={tab==='good'?'active':''}>
  81. <Link to="index" query={{tab:'good'}}>精华</Link>
  82. </li>
  83. <li className={tab==='share'?'active':''}>
  84. <Link to="index" query={{tab:'share'}}>分享</Link>
  85. </li>
  86. <li className={tab==='ask'?'active':''}>
  87. <Link to="index" query={{tab:'ask'}}>问答</Link>
  88. </li>
  89. <li className={tab==='job'?'active':''}>
  90. <Link to="index" query={{tab:'job'}}>招聘</Link>
  91. </li>
  92. </ul>
  93.  
  94. {this.state.data.map(function (item) {
  95. return (
  96. <div className="media">
  97. <div className="media-left">
  98. <Link to="user-detail" params={{loginname:item.author.loginname}}>
  99. <img className="media-object" src={item.author.avatar_url} width="40"
  100. heigth="40" title={item.author.loginname}/>
  101. </Link>
  102. </div>
  103. <div className="media-body">
  104. <h4 className="media-heading">
  105. <Label tab={tab} data={item}/>
  106. <Link to="topic-detail" params={{topicId:item.id}}>{item.title}</Link>
  107. </h4>
  108.  
  109. <p className="media-count">
  110. <i className="fa fa-hand-pointer-o"></i>{item.visit_count}
  111. <i className="fa fa-comment mg-l-5"></i>{item.reply_count}
  112. <i className="fa fa-calendar mg-l-5"></i>发表于{utils.getPubDate(item.create_at)}
  113. </p>
  114. </div>
  115. </div>
  116. )
  117. }.bind(this))}
  118.  
  119. </div>
  120. </div>
  121. )
  122. }
  123. });

看个效果

小结

总体来说开发流程还是比较顺利,当然了因为这里没有涉及到登录问题。如果想在实际开发中使用React,有几个问题不得不面对

  1. 对开发者的要求高,至少要熟悉React,React Router,特别是组件的构建,如何提高复用率?这些都是要在前期思考的。多人开发协作下,这个问题尤其尖锐,一个不好就是一锅粥!

  2. React的第三方组件不够成熟,如果是后端渲染,很多组件不能用,以为它们在代码里直接使用的window、document对象!

  3. 程序是为业务服务的!

就算这样,我还是想还成为那个吃桃子的人!

猜你在找的React相关文章