挨踢部落直播教室第七期

作者: 网络编程  发布:2019-11-14

引言

-

随着手机及移动设备的普及,移动端的应用也进入了热潮。以前PC端的门户网站,大多也均推出了适配移动设备的网站或者APP,再差的也注册了个公众号。在移动应用开发中,目前据我所了解到的解决方案有:1、原生态APP开发;2、适配移动设备的JS HTML网站,套上跨平台的“马甲”;3、微信小程序及公众号(其实和第2点一样,套了微信的壳)。其中运用JS HTML的应用占据大部分,这个比例是多少我没有统计和查询相关数据,反正就是很多吧。而JS HTML移动开发中,大多采用的框架是,前端页面加上WebService。因此,引出我们今天要说的话题:ajax调用handler,使用HttpWebRequest访问WCF服务。说了这么多,其实就是想表明:现在移动开发很热,大家都采用“JS HTML WebService 套娃”这种架构,我分享的这个是给大家在这种架构中开发,提供一个解决方案。还是大白话适合我,不过偶尔也要装装逼,吹吹现在的互联网 时代。

随着前端的发展,为了用户体验,H5越来越多的使用SPA架构,导致JS代码越来越多,体积也变的庞大,这时传统的ajax方式在首屏访问时就变得慢了,而且ajax在seo方面有天然的弱势,这时服务端渲染又回来了。我们使用React搭配React Router等类库来实现服务端渲染,让首屏更快,seo更好。那么,如何使用React构建同构(isomorphic)应用呢,我们特此邀请到百安居前端架构师陈国兴做直播分享。

正文

随着前端的发展,为了用户体验,H5越来越多的使用SPA架构,导致JS代码越来越多,体积也变的庞大,这时传统的ajax方式在首屏访问时就变得慢了,而且ajax在seo方面有天然的弱势,这时服务端渲染又回来了。我们使用React搭配React Router等类库来实现服务端渲染,让首屏更快,seo更好。那么,如何使用React构建同构(isomorphic)应用呢,我们特此邀请到百安居前端架构师陈国兴做直播分享。

言归正传,移动应用的开发采用 “JS HTML WebService 套娃”架构,以下均简称这种架构,前端通过JS(JQuery、React等等)和HTML进行呈现和业务操作,再通过ajax请求后台服务数据,服务端以API形式开发接口(WebService、WCF等等)。.net平台中,服务端采用WCF提供API接口,是不错的选择。出于安全考虑,服务端一般不直接开放给客户端直接调用,客户需通过web服务器,中转调用webservice。中转的程序只写一次就可以了,我这里采用了一般处理程序handler。

内容简介

首先,客户端通过ajax调用handler:

  1. 移动端为什么要用SPA
function Commit1() {
    var req = {};
    req.Name = "测试1";
    req.Age = "30";
    var dvalue = JSON.stringify(req);
    var para = { action: "GetPersonDetail", data: dvalue };
    ajaxWCFService(para, false, CommitSuc);
}

function CommitSuc(data) {
    data = $.parseJSON(data);
    alert("Name:"   data.Name   ",Age:"   data.Age);
}

//调用服务接口
function ajaxWCFService(param, async, sucFunc) {
    var hUrl = document.URL;
    var pathName = document.location.pathname;
    var pos = hUrl.indexOf(pathName);
    var url = hUrl.substring(0, pos);
    url  = "/handle/BaseHandler.ashx";

    $.ajax({
        type: "post",
        url: url,
        data: param,
        async: async,
        success: sucFunc
    });
}

2. 传统ajax方式和服务端渲染加载速度比较

 

  1. 服务端渲染技术详解

Handler做为中转,只开放给网站服务端,对客户端不开放,通过HttpWebRequest访问服务端,我这里采用了WCF:

4. 同构方式的react代码编写一些需要注意的地方

public void ProcessRequest(HttpContext context)
        {
            string action = context.Request["action"];
            string Data = context.Request["data"];
            //Data = "Name=testc&Age=30";
            string url = "http://localhost:28380/Service1.svc/"   action;

            //获取服务端返回信息
            string result = postSend(url, Data);
            context.Response.Write(result);
        }

        public string postSend(string url, string param)
        {
            Encoding myEncode = Encoding.GetEncoding("UTF-8");
            byte[] postBytes = Encoding.UTF8.GetBytes(param);

            HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(url);
            req.Method = "POST";
            req.ContentType = "application/x-www-form-urlencoded;charset=UTF-8";
            req.ContentLength = postBytes.Length;
            try
            {
                using (Stream reqStream = req.GetRequestStream())
                {
                    reqStream.Write(postBytes, 0, postBytes.Length);
                }

                using (WebResponse res = req.GetResponse())
                {
                    using (StreamReader reader = new StreamReader(res.GetResponseStream()))
                    {
                        string result = reader.ReadToEnd();
                        return result;
                    }
                }
            }
            catch (WebException ex)
            {
                HttpWebResponse res = (HttpWebResponse)ex.Response;
                StreamReader reader = new StreamReader(res.GetResponseStream());
                string result = reader.ReadToEnd();
                string IsSucceed = "false";
                string ErrorMsg = "Hander:"   ex.Message   " StackTrace:"   ex.StackTrace "rn ErrorResponse:"   result;
                var success = new { IsSucceed, ErrorMsg };
                return JSONHelper.ToJSON(success);
            }
        }

我们会用到的react、react-router、redux这些库,,代码示例是之前的项目,react-router是2的版本,和最新的API可能会有一些差异。

 

一、移动端为什么要用SPA

接着,WCF服务端首先是契约定义: 

我们先从为什么用SPA说起。这是因为移动互联网的发展。页面的跳转如果使用传统链接跳转的方式,尤其是在2.5G、3G时代,网速慢,不稳定,很容易点击链接后,然后就看到一片白茫茫的页面,运气好,等一会到新的页面,运气不好,那就一直在白页面上。所以需要SPA,至少在网络不好的时候,还可以看到页面,这样用户的体验会比较好。

[OperationContract]
        [WebInvoke(UriTemplate = "GetPersonDetail", Method = "POST", BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Json)]
        Stream GetPersonDetail(Stream req);

因为使用SPA的方式开发,必然导致客户端JS是富客户端的JS,那么就带来一个问题,代码量多了如何管理,以及如何可维护。这就有了早期的BackBone,SpineJs等MVC框架,以及之后的MVP,MVVM等框架,把原来服务端的架构思想逐渐带到前端。目前,以angular、vue、react最为流行。

 

有人会问,为什么不选择angular或者vue?用一句流行的话来说就是:angular(vue)你是个好人,但我们不适合。当然,真正的原因是React的组件化思想刚好和自己想要的匹配,是技术思想上的认同,而react出来时,vue那时还没出来,angular真是又重又复杂。

WCF方法的实现:

二、传统ajax问题和服务端渲染加载速度比较

        public Stream GetPersonDetail(Stream stream)
        {  
            StreamReader sr = new StreamReader(stream);
            string s = sr.ReadToEnd();
            sr.Dispose();
            JObject jo = JObject.Parse(s);
            string Name = jo["Name"].ToString();
            string Age = jo["Age"].ToString();

            var result = new Person()
            {
                IsSucceed = true,
                Name = "Service Back:"   Name,
                Age = "Service Back:"   Age
            };
            return new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(result))); 
        }

我们今天是讲同构,同构首先是服务端渲染(SSR),一般也称为首屏优化。我盗一张图,来看传统的页面渲染流程。

 

图片 1

至此,“JS HTML WebService 套娃”框架基本完成,后面就是添砖加瓦的工作的,只需要写JS页面操作及WCF数据访问即可。

最早的Web开发方式其实是服务端渲染,但是后来大家觉得体验不好,每一次都是要重新刷新页面,这就有了ajax。最初,ajax并没有问题。但是,移动时代来了,JS框架来了。JS变的越来越大了。

好了今天就写到这,如果这篇文章对你有所帮助,请推荐一下!欢迎转截,请注明出处!

从上面的图可以看出,我们要访问一个页面,首先是渲染一个没有数据的空白页面,然后加载资源,比如CSS,JS,一个打包压缩好的JS文件甚至有好几百K。等JS加载完了,这时才发起API请求,用户还得继续等,等到请求回来才能看到一个真正的页面。所以这个时候,反而慢了,这时服务端渲染的方式又回来了。

 

我发个图,极端情况在慢速3G下的访问情况。

图片 2

慢速的3G,没有调用接口的情况,到可正常访问时,总时间在22.94s(不计图片加载)。可以看到,login页面和main.css加载完是在3.96s。

如果是使用服务端渲染,是不需要js即可看到页面的,也就是时间是这里的login页面和css加载完就可以看到真正的页面。而如果是传统ajax方式,则是在22s多,两者有6倍左右的差距,如果再加上接口调用,我们之前测试过,用户看到首屏的的时间,有8-10倍左右的差距。

服务端渲染的首屏时间是:page api request css,page已经包含数据了。
客户端的首屏时间是:page css js api request。

除了客户端需要加载一个很大的js文件外,API请求在服务端进行一般也是更快的。这里简单解释一下首屏的概念:非首页。从任何地方进来的那个页面都是首屏。也就是说,做isomorphic,首先要保证没有js的情况,可以直接从浏览器输入任何一个地址进行访问也是可访问的。和早期的服务端渲染是一样的。所以,这也是为什么SEO能更友好的原因。

三、服务端渲染技术详解

为什么要使用客户端与服务端复用代码的同构方式?维护性问题。客户端是不安全的,所以服务端不能信任客户端,需要做各种校验,包括拉取数据后的ui渲染,这样就需要前后端都要写一次一样逻辑的代码。为了开发效率、维护性等,所以需要复用。

这点上,nodejs有天然的优势。如果不考虑同构的话,光服务端渲染,其实很简单,react提供了一个方法:renderToString()。只要把它取得的数据塞到模版文件里就可以了,比如nodejs的ejs文件。为了代码复用,我们会考虑ui放服务端渲染,逻辑放服务端,API请求的代码也共用一套,路由最好也是只写一次。react router它就支持服务端路由,并且它也为服务端渲染提供了一些友好的API,比如Link。

接下来,我们就把具体的代码大概讲一下。首先是路由定义。

图片 3

这里,history属性在浏览器端与服务端是不一样的,所以需要传进来。浏览器端使用browserHistory:

import { browserHistory } from 'react-router'

图片 4

服务器端使用createMemoryHistory:

import { RouterContext, createMemoryHistory, match } from 'react-router'

我们把服务器端(nodejs)的路由配置全部贴出来,其实使用的是react-router提供的方法。

server.get('*', (req, res, next) => {   const history = createMemoryHistory()   const routes = createRoutes(history)   let store = configStore()    match({ routes, location: req.url }, (err, redirectLocation, renderProps) => {     if (err) {       res.status(500).send(err.message)     } else if (!renderProps) {       res.status(404).send('page not found')     } else {       getComponentFetch(renderProps, history, store).then(() => {         let reduxState = escape(JSON.stringify(store.getState()))         let html = ReactDOM.renderToString(           <Provider store={store}>             {<RouterContext {...renderProps} />}           </Provider>           )         res.render('home', { html, scriptSrcs, cssSrc, reduxState })       })       .catch((err) => {         next(err)       })     }   }) })  function getComponentFetch (renderProps, history, store) {   let { query, params } = renderProps   let component = renderProps.components[renderProps.components.length - 1].WrappedComponent   let promise = component && component.fetchData ? component.fetchData({ query, params, store, history }) : Promise.resolve()   return promise }

路由匹配所有请求,当访问时,根据路由配置,取得对应的react组件,因为要在服务端马上调用API接口获取数据,我们会在容器组件放一个静态方法:fetchData,调用这个方法来取得数据,然后放在一个变量传给ejs模版文件。当然,我们这时页面已经渲染出数据了。这个reduxState变量的数据是做为js加载完后 渲染时使用。

我们看一下客户端的代码:

let reduxState = {} if (window.__STATE__) {   try {     reduxState = JSON.parse(unescape(__STATE__))   } catch (e) {   } } const store = configStore(reduxState)   ReactDOM.render((     <Provider store={store}>       {createRoutes(browserHistory)}     </Provider>     ), document.getElementById('container-root'))

window.__STATE__ 这个就是我从服务端传过来的变量reduxState的值,用来初始化redux的store。

同时,如果为了避免首屏服务端请求一次数据,浏览器又再请求一次数据,我们可以把当前的container组件的displayName也从服务端传回浏览器端,这样在组件里判断有值,则不发起fetch请求,而是直接使用的是redux store的值。

fetchData的大概代码我也贴一下:

static fetchData ({store}) {     let cityId = global.currentCityId     return store.dispatch(actions.getHomeData(cityId))   }

本文由金沙澳门官网发布于网络编程,转载请注明出处:挨踢部落直播教室第七期

关键词: 金沙澳门官网