资讯专栏INFORMATION COLUMN

React Server Render

boredream / 931人阅读

摘要:当我们在浏览器端进行切换切换的时候,页面是不刷新的,通过请求获取到数据,重新渲染结构。如果想在实际开发中使用,有几个问题不得不面对对开发者的要求高,至少要熟悉,,特别是组件的构建,如何提高复用率这些都是要在前期思考的。

概述

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

搭建


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

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

lib 后端代码,如过滤器等

node_modules 依赖包

public 静态资源

routes 路由

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

module.exports = {
    state: {
        SUCCESS: 10000
    }     
};

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

module.exports = {
    state: {
        SUCCESS: 10000
    }     
};

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

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

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

编码

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

route

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

var constants = require("../lib/constants");
var request = require("superagent");
var queryString = require("query-string");

module.exports = {
    get: {
        "/": function (req, res) {
            request
                .get("http://cnodejs.org/api/v1/topics?" + queryString.stringify(req.query))
                .end(function (err, response) {
                    if (err) {
                        throw err;
                    }
                    res.render(req.url, {
                        state: constants.state.SUCCESS,
                        data: response.body.data,
                        title: "CNode:Node.js专业中文社区"
                    });

                });
        }
    }
};

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

/**
 * 区分ajax请求与普通请求
 */
req.isXmlHttpRequest = (function () {
    var xRequestedWith = req.headers["x-requested-with"];
    return xRequestedWith && xRequestedWith.toLowerCase() === "xmlhttprequest";
})();

/**
 * 重写res.render方法
 */
var render = res.render;

res.render = function (view, data) {
    var response = _.extend({session: req.session}, data);
    req.isXmlHttpRequest ? res.json(response) : render.call(res, view, response);
};

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

组件

来看看我们打包的入口

component/index.js
var React = require("react");
var Router = require("react-router");
var $ = require("jquery");
var Routes = require("./routes.jsx");

var CLIENT_VARIABLENAME = "__REACT_ENGINE__";

var _window;
var _document;
if (typeof window !== "undefined" && typeof document !== "undefined") {
    _window = window;
    _document = document;
}

document.addEventListener("DOMContentLoaded", function onLoad() {
    Router.run(Routes, Router.HistoryLocation, function onRouterRun(Root, state) {
        var props = _window[CLIENT_VARIABLENAME];
        if (props) {
            var componentInstance = React.createElement(Root, props);
            React.render(componentInstance, _document);
            _window[CLIENT_VARIABLENAME] = null;
        } else {
            $.get(state.path).then(function (data) {
                var componentInstance = React.createElement(Root, data);
                React.render(componentInstance, _document);
            });
        }

    });
});

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

component/routes.jsx

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

var React = require("react");
var Router = require("react-router");

var Route = Router.Route;
var DefaultRoute = Router.DefaultRoute;

var App = require("./app.jsx");
var Index = require("./views/index.jsx");

var TopicDetail = require("./views/topic/detail.jsx");
var UserDetail = require("./views/user/detail.jsx");

var routes = (
    
        
        
        
    
);

module.exports = routes;

都是些基本的路由配置

component/app.jsx

再来看下入口组件

var React = require("react");
var Router = require("react-router");

var Layout = require("./views/layouts/default.jsx");

var RouteHandler = Router.RouteHandler;


module.exports = React.createClass({
    render: function () {
        var data = this.props.data;
        return (
            
                
            
        )
    }
});

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

var React = require("react");
var constants=require("../../../lib/constants");

var Footer=require("../partials/footer.jsx");

module.exports = React.createClass({
    render: function render() {
        return (
            
            
                {this.props.title}
                
                
                
                
                
                
                
            
            
            {this.props.children}
            
); } });
component/views/index.jsx

这里就是业务代码了

var React = require("react");
var Router = require("react-router");
var $ = require("jquery");
var Navbar = require("./partials/navbar.jsx");
var queryString = require("query-string");
var utils=require("../component/utils");

var Link = Router.Link;


var Label = React.createClass({
    render: function () {
        var tab = this.props.tab;
        var data = this.props.data;

        if (data.top) {
            return ;
        }

        if (data.good) {
            return ;
        }

        if (!tab || tab === "all") {
            if (data.tab === "share") {
                return ;
            }

            if (data.tab === "ask") {
                return ;
            }

            if (data.tab === "job") {
                return ;
            }
        }

        return null;
    }
});

module.exports = React.createClass({
    getInitialState: function () {
        return {
            data: this.props.data || [],
            page: 1
        }
    },
    componentWillReceiveProps: function (nextProps) {
        this.setState({
            data: nextProps.data,
            page: 1
        });
    },
    componentDidMount: function () {
        var loading = false;
        $(window).on("scroll", function () {
            var fromBottom = $(document).height() - $(window).height() - $(window).scrollTop();

            if (fromBottom <= 10 && !loading) {
                loading = true;
                var query = queryString.parse(location.search);
                query.page = this.state.page + 1;
                $.get(location.pathname + "?" + queryString.stringify(query), function (response) {
                    this.setState({
                        data: this.state.data.concat(response.data),
                        page: this.state.page + 1
                    }, function () {
                        loading = false;
                    });
                }.bind(this));
            }
        }.bind(this));
    },
    render: function () {
        var tab = this.props.query.tab;
        return (
            
  • 全部
  • 精华
  • 分享
  • 问答
  • 招聘
{this.state.data.map(function (item) { return (

{item.visit_count} {item.reply_count} 发表于{utils.getPubDate(item.create_at)}

) }.bind(this))}
) } });

看个效果

小结

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

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

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

程序是为业务服务的!

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85983.html

相关文章

  • React的元素、组件、事件、props传递

    摘要:如果你不想这样每次改完代码都要手动编译,而且自己启动,那么就要用到这个玩意。 通过配置webpack-dev-server启动一个webserver,自动编译,自动刷新浏览器的功能,我们开启React基础速过 接上一篇:构建React开发环境 使用webpack-dev-server 按照上篇文章构建好React开发环境后,我们发现每次写完代码还需要手动编译,并且需要自己启动一个web...

    BlackMass 评论0 收藏0
  • 从零开始搭建React同构应用(三):配置SSR

    摘要:从零开始搭建同构应用三配置这篇文章来讲解来配置,我们先从最简单的方法开始,用的方式模拟实现。影响生产环境下执行效率。最后权衡下,还是决定使用现在多一套编译配置的方案。新建,写入以下内容以为例,注意不能少。 从零开始搭建React同构应用(三):配置SSR 这篇文章来讲解来配置server side render,我们先从最简单的方法开始,用cli的方式模拟实现SSR。 demo在这里 ...

    jzzlee 评论0 收藏0
  • 《慕课React入门》总结

    摘要:入门与实战组件虚拟的概念这是性能高效的核心算法为此引入了虚拟的机制。这个过程是自动完成的。实际上是改变了样式文件中类的名称,使其唯一。如果希望使用达到的效果,则需要做件事情服务器支持。 React 入门与实战 react组件 虚拟DOM的概念 这是React性能高效的核心算法 React为此引入了虚拟DOM(Virtual DOM)的机制。基于React进行开发时所有的DOM构造都是通...

    NotFound 评论0 收藏0
  • 《慕课React入门》总结

    摘要:入门与实战组件虚拟的概念这是性能高效的核心算法为此引入了虚拟的机制。这个过程是自动完成的。实际上是改变了样式文件中类的名称,使其唯一。如果希望使用达到的效果,则需要做件事情服务器支持。 React 入门与实战 react组件 虚拟DOM的概念 这是React性能高效的核心算法 React为此引入了虚拟DOM(Virtual DOM)的机制。基于React进行开发时所有的DOM构造都是通...

    zhigoo 评论0 收藏0
  • 从零开始,揭秘React服务端渲染核心技术

    摘要:不过这时的控制台会抛出这样一则警告提醒我们在服务端渲染时用来取代,并警告我们在时将不能用去混合服务端渲染出来的标签。综上所述,服务端和客户端都是需要路由体现的。我们画一下重点,意思很明确,就是为了服务端渲染而打造的。 抛砖引玉 在早几年前,jquery算是一个前端工程师必备的技能。当时很多公司采用的是java结合像velocity或者freemarker这种模板引擎的开发模式,页面渲染...

    googollee 评论0 收藏0

发表评论

0条评论

boredream

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<