1. 程式人生 > >【實踐】用路由為webApp單頁應用提供多入口

【實踐】用路由為webApp單頁應用提供多入口

前言

我1個月前開發的webApp是個單入口的單頁應用,那個時候需求沒有很複雜,僅僅是一個購物的站點,但後來不停地新增功能,而且近期還提出需要把webAPP的部分頁面分享到 IOS webView、微信朋友圈,並且能完成完整的業務邏輯。

我的策略

拆模組,分多頁

這個需求下來,我第一反應是“拆”——把單頁的webAPP根據模組拆分成多頁,以一個新的html頁面的形式提供給iOS和微信端。這麼做的好處很明顯:

  1. 思路簡單,不需要很複雜的js架構
  2. 可以利用瀏覽器的歷史記錄來做返回跳轉,避免了原先的靠判斷狀態碼來實現 返回 更簡便且易維護。
  3. 以模組拆分頁面,方便團隊開發和維護。

此法雖好,但是不太適合眼前的情況:

  1. 放棄單頁的思路,那麼之前的架構就得重新整合,這會是一個大工程,因為現有的程式碼光js已經超過了4500行,公司追求速度,不太情願花過多的時間在架構上。

這裡寫圖片描述

  1. 放棄單頁的思路,那麼對模組化的要求就變得更高,單頁內資源共享的優勢就蕩然無存,需要花費不少時間去封裝原有的模組,單一職責,充分解耦。這又是一個時間投入比較大的工作,公司又不情願了。

  2. 當前公司人才短缺,整個webAPP專案能實際參與開發的就我一人,而我,不久前也找好了新東家,明年過完春節就去入職,所以留給這個專案的時間已然不多。雖然條件苛刻,但還是要去做。

如何在有限的時間裡 把這個問題解決呢?我 最後想來想去,覺得 可以參考 AngularJS裡 路由

的思路。

換路由,多入口

之前做專案架構的時候我還沒來得及學習AngularJS,所以在設計上有不少的“笨思路”,沒有很好地站在巨人的肩膀上去思考問題。唉,好在現在已經意識到這一點了,以後做每件事都不應該“閉門造車”,而且多參考一些成功的案例,借鑑別人成功的經驗,少走彎路才好。

路由的好處:

  1. 以url作為頁面跳轉的媒介,這樣的思路可能和 原先的 狀態碼 類似,但是狀態碼是隻能 通過JS內部呼叫,無法外部操作,而url則不然,不僅可以外部操作,多口入進入,而且對SEO友好性上 也提升了一些。

  2. 改用路由的方式,原先的很多模組不用做太多的改動,僅僅是 controller層的 狀態碼

    寫入需要做相應的改動。 這樣就節省了 “大手術”的開發成本和風險。

可能方法沒有好壞之分,只有適合不適合當前的現狀吧。偉人云“黑貓白貓能抓到老鼠的就是好貓”,這一理論在小公司特別明顯。沒辦法,我們的專案需求變動地很快,老闆要求的時間也很緊張,沒有很多的時間讓我去學習和思考技術的好壞,只要能快速地出成果就OK。這樣的現狀使得小公司出來的技術人才在技術理論層面或者深度上會不及大公司的同類人才,但是論專案經驗和碰壁的次數也是沒的比的。在這個公司,作為前端的我,不僅要把前端的工作 做好,PS、Linux、PHP、Java方面的問題都要接觸不少。而我們的原則就是“既然碰壁,那就努力把它碰穿”,無論什麼問題,解決就可以了!

既然目前的趨勢是“大前端”,那我並不覺得小公司出來的人才在能力上會比大公司出來的差,還是要看一個人的品質和他努力的程度的,我始終相信成功靠堅持,一個人肯在一個領域裡不停地摸索滾爬,那麼終有一天能成為該領域的領軍人物的。

具體實施

get方式做路由

get方式的url引數傳遞是比較好理解的,而且取值操作也很容易,重要的是這樣的引數方式不會對頁面產生任何影響。

function queryString(){
    var url = location.search;
    var theRequest = new Object();
    if (url.indexOf("?") != -1) {
        var str = url.substr(1);
        strs = str.split("&");
        for(var i = 0; i < strs.length; i ++) {
            theRequest[strs[i].split("=")[0]]=unescape(strs[i].split("=")[1]);
        }
    }
    return theRequest;
}

當然,僅僅針多webAPP這種單頁應用來說,沒有所謂的get方式提交表單的問題,因為所有的資料都是通過ajax非同步載入和渲染的。但是從長遠的角度考慮,這樣的方式有點“越界”的意思,get方式的初衷是資料請求,現在卻用它做路由總感覺職責不明,是不是有很好的方式呢?

雜湊方式的路由

大家知道 html裡有個 錨點連結 的概念吧,通過 #錨點名來實現頁面內部的跳轉。這個其實就是最原始的雜湊方式的路由了,不過僅僅是用於頁面內部,而單頁應用也是一個頁面,內部的其他頁面是通過js控制顯示和隱藏的,這使得 雜湊方式的路由思路上十分自然。

當然,借鑑ThinkPHP、springMVC、express等框架對路由的設計,我們可以把我們的路由搞得更高大上些:

var app= {};
app.Router = function(){
    function Router(){
    }
    Router.prototype.setup = function(routemap, defaultFunc){
        var that = this, rule, func;
        this.routemap = [];
        this.defaultFunc = defaultFunc;
        for (var rule in routemap) {
          if (!routemap.hasOwnProperty(rule)) continue;
          that.routemap.push({
            rule: new RegExp(rule, 'i'),
            func: routemap[rule]
          });       
        }
    };
    Router.prototype.start = function(){
        console.log(window.location.hash);
        var hash = location.hash, route, matchResult;
        for (var routeIndex in this.routemap){
            route = this.routemap[routeIndex];
            matchResult = hash.match(route.rule);
            if (matchResult){
                route.func.apply(window, matchResult.slice(1));
                return; 
            }
        }
        this.defaultFunc();
    };
    return Router;
}();
var router = new app.Router();
router.setup({
    '#/list/(.*)/(.*)': function(cate, id){
        console.log('list', cate, id);
    },
    '#/show/(.*)': function(id){
        console.log('show', id); 
    }
}, function(){
    console.log('default router');
});
router.start();

線上例項

html結構:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>js實現簡單的路由</title>
    <link rel="stylesheet" type="text/css" href="./style.css">
</head>
<body>
    <h1>開啟路由功能</h1>
    <ul>
        <li><a href="javascript:void(0)" id="open-page1">開啟第一個頁面</a></li>
        <li><a href="javascript:void(0)" id="open-page2">開啟第二個頁面</a></li>
        <li><a href="javascript:void(0)" id="open-page3">開啟第三個頁面</a></li>
    </ul>

    <div id="page1" class="bg1">這裡是頁面一</div>
    <div id="page2" class="bg2">這裡是頁面二</div>
    <div id="page3" class="bg3">這裡是頁面三</div>

    <script type="text/javascript" src="./jquery.min.js"></script>
    <script type="text/javascript" src="./router.js"></script>
</body>
</html>

css結構:

body{
    text-align: center;
    font-size: 12px;
    color: #555;
    font-family: '微軟雅黑';
    padding-top: 30px;
}
li{
    margin-top: 10px;
}
div{
    width: 800px;
    height: 600px;
    display: none;
    margin: 20px auto;
    padding-top: 20px;
}
div.bg1{
    background: rgba(238,34,102,0.5);
}
div.bg2{
    background: #f0f0f0;
}
div.bg3{
    background: rgba(0,145,230,0.5);
}

js結構:

$(function(){
  var app = {};
  app.Router = function(){
    function Router(){
    }
    Router.prototype.setup = function(routemap, defaultFunc){
      var that = this, rule, func;
      this.routemap = [];
      this.defaultFunc = defaultFunc;
      for (var rule in routemap) {
        if (!routemap.hasOwnProperty(rule)) continue;
        that.routemap.push({
          rule: new RegExp(rule, 'i'),
          func: routemap[rule]
        });       
      }
    };
    Router.prototype.start = function(){
      var hash = location.hash, route, matchResult;
      for (var routeIndex in this.routemap){
        route = this.routemap[routeIndex];
        matchResult = hash.match(route.rule);
        if (matchResult){
          route.func.apply(window, matchResult.slice(1));
          return; 
        }
      }
      this.defaultFunc();
    };
    return Router;
  }();
  var router = new app.Router();
  router.setup({
    '#/list/(.*)/(.*)': function(cate, id){
        console.log('list', cate, id);
      },
    '#/show/(.*)': function(id){
        console.log('show'+id);
        $('div').hide();
        $('#'+id).show();
      }
  }, function(){
    console.log('default router');
  });
  app.operate=(function(){
    var btns=[$('#open-page1'),$('#open-page2'),$('#open-page3')];
    $.each(btns,function(idx,item){
      var pageIdx=idx+1;
      item.on('click',function(){
        var oldUrl=location.href.split('#')[0];
        location.replace(oldUrl+"#/show/page"+pageIdx);        
        router.start();
      });
    });
  })();
});