淺談HTML5單頁面架構(三)—— 迴歸本真:自定義路由 + requirejs + zepto + underscore

本文轉載自:http://www.cnblogs.com/kenkofox/p/4650310.htmlhtml

不過,這一篇,我想進一步探討一下這兩個框架的優缺點,另外,再進一步,拋開這兩個框架,回到本真,本身搞個簡單的路由同樣能夠實現單頁面。前端

這個對於剛作前端開發的新同窗來講就最好不過了,若是一來到崗位就一大堆angular、backbone、requirejs,看資料都看一兩週。其實你們最熟悉的東西仍是那個美圓$,用美圓能解決的問題,就不要麻煩到angular、backbone大爺了。jquery

 

事先說明,因爲個人業務範圍窄,不必定能把angular和backbone的功能都用一遍,因此如下的分析可能以偏概全,歡迎你們討論。git

angular優勢:github

  • 強大的數據雙向綁定
  • View界面層組件化
  • 內置的強大服務(例如表單校驗)
  • 路由簡單

angular缺點:web

  • 引入的js較大,對移動端來講有點吃不消
  • 語法複雜,學習成本高

backbone優勢:正則表達式

  • 引入的js較小
  • 清晰MVC分層
  • Model層事件機制
  • 路由簡單並且便於擴展

backbone缺點:閉包

  • MVC有點死板,有時候以爲累贅
  • 沒有雙向綁定,界面修改只能靠本身
  • view切換時,沒有足夠便捷的事件通知(要本身監聽route)

其實,這兩個框架都很是優秀,可是,在實際業務中,不必定百試百靈,由於有一些移動端的單頁面web,業務就很簡單,只是路由分別切換到幾個子模塊,每一個子模塊基本都是拉一次數據,展現給用戶,不多用戶交互從而修改數據,改變視圖的功能。架構

對於這種狀況,使用angular未免有點殺雞用牛刀的感受,而backbone雖然小巧了很多,可是模型的功能也是浪費的。app

因此,在這裏,我想探討一下,可否拋開這兩個框架,只索取咱們基本所需,創建一個更簡單的架構呢?

經驗看來,一些類庫是必不可少的:

  • requirejs:模塊劃分
  • zepto:移動端的jquery
  • underscore:便捷的基礎方法,包括模版template、each、map等等
  • 路由庫:這裏先使用director.js,然而這玩意並無backbone和angular的路由好用,文章最後再來探討這個問題

 

本身作一套最簡單的架構,思想很是簡單:

  1. 啓動程序
  2. 監聽路由
  3. 路由變化,映射到對應的處理邏輯,加載對應的模塊
  4. 模塊加載完成,修改dom,也就是視圖
  5. 頁面跳轉時,移除上一個模塊,加載下一個模塊,也就是回到第3點

 

簡單的思路,讓架構很是簡潔明瞭,新團隊成員來到可以輕鬆上手,而angular和backbone的架構,少說得二、3天才能融入一個已有項目中去。

接下來,咱們具體看看怎麼作。

 

第一步,仍是index.html

複製代碼
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Underscore & Director & Requirejs</title>
</head>

<body>
<div id="container"></div>
<script data-baseurl="./" data-main="main.js" src="libs/require.js" id="main"></script>
</body>
</html>
複製代碼

這個跟前兩篇沒什麼差異。requirejs引入main.js做爲程序入口

 

第二步,main.js配置requirejs的依賴關係,並啓動webapp

複製代碼
(function (win) {
    //配置baseUrl
    var baseUrl = document.getElementById('main').getAttribute('data-baseurl');

    /*
     * 文件依賴
     */
    var config = {
        baseUrl: baseUrl,           //依賴相對路徑
        paths: {                    //若是某個前綴的依賴不是按照baseUrl拼接這麼簡單,就須要在這裏指出
            director: 'libs/director',
            zepto: 'libs/zepto.min',
            underscore: 'libs/underscore',
            text: 'libs/text'             //用於requirejs導入html類型的依賴
        },
        shim: {                     //引入沒有使用requirejs模塊寫法的類庫。
            underscore: {
                exports: '_'
            },
            zepto: {
                exports: '$'
            },
            director: {
                exports: 'Router'
            }
        }
    };

    require.config(config);
    require(['zepto', 'router', 'underscore'], function($, router, _){
        win.appView = $('#container');      //用於各個模塊控制視圖變化
        win.$ = $;                          //暴露必要的全局變量,不必拘泥於requirejs的強制模塊化
        win._ = _;
        router.init();                      //開始監控url變化
    });


})(window);
複製代碼

director.js沒有AMD寫法,仍是按照shim的方式引入。另外,因爲$和_的使用率過高,因此這裏直接公開爲全局變量。

除此以外,還加了appView變量,目的是方便各個子模塊修改界面。

 

第三步,router.js配置路由

這裏使用的路由類庫是director(https://github.com/flatiron/director),相對精簡的路由,但其實對於咱們這個程序來講,貌似還不夠精簡。先湊合着吧。

director官網給出的示例也至關簡單,就是「路徑」對應「函數」,很是清晰並且實用的方式。

複製代碼
      var author = function () { console.log("author"); };
      var books = function () { console.log("books"); };
      var viewBook = function (bookId) {
        console.log("viewBook: bookId is populated: " + bookId);
      };

      var routes = {
        '/author': author,
        '/books': [books, function() {
          console.log("An inline route handler.");
        }],
        '/books/view/:bookId': viewBook
      };

      var router = Router(routes);

      router.init();
複製代碼

 

來看看咱們本身的版本:

複製代碼
define(['director', 'underscore'], function (Router, _) {

    //先設置一個路由信息表,能夠由html直出,純字符串配置
    var routes = {
        'module1': 'module1/controller1.js',
        'module2/:name': 'module2/controller2.js'     //director內置了普通必選參數的寫法,這種路由,必須用路徑「#module2/kenko」才能匹配,沒法缺省
//        'module2/?([^\/]*)/?([^\/]*)': 'module2/controller2.js'    //可缺省參數的寫法,其實就是正則表達式,括號內部分會被抽取出來變成參數值。backbone作得比較好,把這個語法簡化了
                                                                    //  「 /?([^\/]*) 」  這樣的一段表示一個可選參數,接受非斜槓/的任意字符
    };

    var currentController = null;

    //用於把字符串轉化爲一個函數,而這個也是路由的處理核心
    var routeHandler = function (config) {
        return function () {
            var url = config;
            var params = arguments;
            require([url], function (controller) {
                if(currentController && currentController !== controller){
                    currentController.onRouteChange && currentController.onRouteChange();
                }
                currentController = controller;
                controller.apply(null, params);
            });
        }
    };

    for (var key in routes) {
        routes[key] = routeHandler(routes[key]);
    }

    return Router(routes);
});
複製代碼

這裏把director的路由配置修改了一下,原來只能接受<String, Function>這樣的key value對,但參考以前backbone篇,更好方式應該是讓路由表儘可能只有字符串配置,不要寫邏輯(函數)。

因此,上述代碼中,多了一個routeHandler,目的就是創建閉包,把string(配置)轉換爲一個閉包函數。

結果,運行效果就是,遇到一個路由,就根據配置加載對應的子模塊代碼。後續實際執行什麼,由子模塊本身決定。這樣main/router就能完全跟子模塊解耦。

 

第四步,創建一個模塊

tpl.html

<div>
    Here is module 1. My name: <%=name %><br>
    <a href="#module2/fromModule1">turn to module 2</a>
</div>

controller1.js

複製代碼
define(['text!module1/tpl.html'], function (tpl) {

    var controller = function () {
        appView.html(_.template(tpl, {name: 'kenko'}));
    };
    return controller;
});
複製代碼

我以爲能實現業務邏輯的前提下,越簡單的架構就越好,便於傳承和維護。

controller就是這個子模塊要作的邏輯,appView是整個視圖根節點,想怎麼玩就怎麼玩,這對於不熟悉angular、backbone的同窗最爽不過了。

這裏重點是利用了requirejs作模塊化和依賴加載,並用了underscore的模版庫template。

 

第五步,再作一個模塊,加上一些銷燬接口

tpl.html

<div>
    Here is module 2. My name: <%=name %><br>
    <button>click me!</button>
    <a href="#module1">turn to module 1</a>
</div>

controller2.js

複製代碼
define(['text!module2/tpl.html'], function (tpl) {

    var controller = function (name) {
        appView.html(_.template(tpl, {name: name?name:'vivi'}));

        $('button').on('click', function clickHandler() {
            alert('hello');
        });

        controller.onRouteChange = function () {
            console.log('change');      //能夠作一些銷燬工做,例如取消事件綁定
            $('button').off('click');   //解除全部click事件監聽
        };
    };

    return controller;
});
複製代碼

 

至此,整個簡單的框架就完成了。

大道至簡,我很是喜歡這樣簡單的架構。但願對新手朋友有所幫助。

 

最後,關於director的路由,要吐槽一下,這個並無backbone那些這麼好用,它沒有內置的缺省參數寫法,須要本身理解正則表達式,寫複雜的([?*。參照上邊router.js的代碼。

路由匹配的本質,實際上是正則表達式的exec匹配和提取參數。我後續會再整一個簡單好用的路由,參考backbone的模式,猛擊這裏:http://www.cnblogs.com/kenkofox/p/4650824.html

 

本文代碼:https://github.com/kenkozheng/HTML5_research/tree/master/UnderscoreRequireJS

相關文章
相關標籤/搜索