使用MVVM框架(avalonJS)進行快速開發


背景

在運營活動開發中,因為工作的重復性很大,同時往往開發時間短,某些情況下也會非常緊急,導致了活動開發時間被大大壓縮,同時有些活動邏輯復雜,數據或者狀態變更都需要手動渲染,容易出錯,正是因為這些問題的存在,所以才有了MV*框架的誕生,比如大名鼎鼎的angularJS。今天就跟大家講講國產的MVVM框架avalonJS是如何快速進行開發的,同時大家也可以對比石器時代的開發模式(jquery或者zepto)與mv*模式的區別。

avalonJS簡介

 avalonJS是前端大牛司徒正美開發和維護的mvvm框架,它是一個基於Model驅動的開發框架,DOM操作近乎絕跡,可以讓前端人員脫離DOM的苦海,來到數據的樂園,相比angularJS它有如下優勢:
1.無任何依賴,壓縮后只有50多kb,而angular的min版有100多kb;

2.爽快的編程體驗,不再糾結於DOM操作;

3.兼容到IE6+,符合天朝國情;

4.效率更高,跑起來比angular和knockout都要更快,在移動端上該優勢會更大(avalon有移動端專版的avalon.modern.js)。關於其性能更詳細的介紹可以看這里

5.涵蓋了angular的大部分功能,且實現方式更為便捷、上手更容易;

相關文檔

GitHub(下載最新的avalon以及實例(examples文件夾里),通過實例來掌握某些功能的實現是很好的學習途徑)

Avalon快速入門(比較快捷的入門課程,只用了幾篇文章來介紹了最常用的一些功能)

API文章(正美的博文,篇幅較大,涵蓋知識點很多,可以當作API來查閱),也可以在這里查看更規范的API。

Avalon亂燉(強烈推薦,用了20多篇文章較詳細地、漸進地介紹avalon)

Avalon入門視頻(推薦)

開始 

這里使用avalon的版本是移動端avalon.modern.shim.js-1.4.1版本,已經存在cdn上(http://imgcache.gtimg.cn/club/common/lib/avalon.js),需要使用直接引入即可,如下:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>avalon初探</title>
</head>
<body>
<div></div>

<script src="http://imgcache.gtimg.cn/c/=/club/mobile_web/zepto.min.js,/club/common/lib/zero/zero.m.min-5.1.1.js,/club/common/lib/avalon.js?max_age=86400000"></script>
</body>
</html>

這里我們引入了zepto,zero以及avalon.js,因為zero依賴zepto,所以這幾個文件必須要引入。

接着,類似於ng的“ng-controller”,avalon的控制域屬性名叫做“ms-controller”,你可以把它當作一個監聽器,把它綁定到一個容器后,avalon就能掃描和監聽這個容器內所有(綁定了avalon方法或帶有插值表達式的)元素了。

我們給這個div加上這個監聽器,並在里面寫一個avalon插值表達式{{a}};

<div ms-controller="wrap">{{a}}</div>

你現在運行的話頁面沒有任何效果,因為我們還沒有寫腳本讓avalon工作起來,我們可以來一段簡單代碼讓其運行起來:

<div ms-controller="wrap">{{a}}</div>

<script src="http://imgcache.gtimg.cn/c/=/club/mobile_web/zepto.min.js,/club/common/lib/zero/zero.m.min-5.1.1.js,/club/common/lib/avalon.js?max_age=86400000"></script>
<script>
    var model = avalon.define('wrap', function (vm) { //model是隨便命名的,用作該Model的載體,wrap為avalon的作用域名稱
        vm.a = '你好啊';                              //a為avalon定義的一個屬性,其值為“你好啊”
    });
    avalon.scan();                                   //開啟avalon掃描,這句話必須要加
</script>
</body>

在avalon中我們使用avalon.define('xx', function (vm) {})來定義一個Model實例,其中xx為所要掃描和監控的控制域名。

我們還在內部定義了一個屬性“a”,故在對應的控制域(對應為ms-controller=“wrap”的div)里 ,我們使用avalon插值表達式{{a}}的話,可以自動綁定其值“你好啊”。

上述代碼運營效果如下:

數據和視圖同步

上方我們實現了非常簡單的數據綁定,將一個avalon屬性a綁定到DOM元素上。不過,avalon更有意思和實用的功能是實現了視圖和數據的同步,說的簡單點,我們用腳本修改了a的值,那么DOM上綁定的數據也會跟着改變(反過來也一樣)

<div ms-controller="wrap">
    <span>{{a}}</span>
    <input ms-duplex="a" />
</div>

<script src="http://imgcache.gtimg.cn/c/=/club/mobile_web/zepto.min.js,/club/common/lib/zero/zero.m.min-5.1.1.js,/club/common/lib/avalon.js?max_age=86400000"></script>
<script>
    var model = avalon.define('wrap', function (vm) {
        vm.a = '你好啊';                            
    });
    avalon.scan();                                 
</script>
</body>

注意這里我們添加了一個<input ms-duplex="a" />,其中ms-duplex是avalon的雙工綁定的屬性,它除了負責將VM中對應的值(如本例是a)放到表單元素的value中,還偷偷對元素綁定一些事件,用於監聽用戶的輸入從而自動刷新VM。

執行代碼如下:

運營頁面的開發

有了以上對avalon的基本了解,我們來看下在運營活動中如何使用avalon快速開發運營活動。

這里以QGC情報站為例,這個頁面完全是一個靜態頁面,所有的數據都是在ams上手動配置的,先看下頁面截圖:

一個如此簡單的頁面,如果我們使用zepto來處理,可能會涉及到各種字符串拼接,然后通過innerHTML的方式插入到指定的div內,這些過程勢必要選擇多個dom,然后操作dom,如果還需要處理一些其他細節需求,比如根據ams有沒有配置更多鏈接,來動態決定是否顯示“更多數據”按鈕,等等等等。我們發現使用zepto的方式來寫代碼很繁瑣,更新頁面狀態時總是需要首先獲取到dom,然后對dom進行其他操作,如果一個dom有多種狀態,又在不同地方展示,那我們的代碼量就會遞增式增長,同時代碼可讀性差,不易維護。

而使用avalon來處理,我們處理的僅僅是數據,代碼量不僅會大幅減少,而且代碼結構會更清晰,也更易維護。

直接上代碼:

html代碼:

<div class="act-wrapper" ms-controller="main">
    <div class="act-content">
        <div class="act-header">
            <h1 class="hide">全民競技大賽</h1>
            <div class="nav">
                <a href="javascript:" ms-class="cur:index==$index" ms-repeat="curGames" ms-click="switchTab($index, el.appid)">{{el.gameName}}</a>
            </div>
        </div>

        <div class="act-game">
            <div class="act-main">
                <div class="act-block act-news">
                    <div class="title">
                        <b class="t1"><strong class="hide">火線禮包</strong></b>
                        <a href="javascript:" ms-click="openUrl(curOnlineLink.link)">賽事直播>></a>
                    </div>
                    <div class="cnts">
                        <div class="item" ms-repeat="curNews">
                            <a href="javascript:" ms-click="openUrl(el.link)" ms-if="$index==0"><img ms-attr-src="el.pic" width="281"/></a>
                            <a href="javascript:" ms-click="openUrl(el.link)" ms-if="$index">
                                <div class="thumb"><img ms-attr-src="el.pic" width="80"/></div>
                                <p>{{el.title}}</p>
                            </a>
                        </div>
                    </div>
                </div>

                <div class="act-block act-ranking">
                    <div class="title">
                        <b class="t2"><strong class="hide">賽事數據</strong></b>
                        <a href="javascript:" ms-click="openUrl(curDataMoreLink.link)" ms-if="curDataMoreLink.link!=''">更多數據>></a>
                    </div>
                    <div class="cnts">
                        <div class="item" ms-repeat="curData">
                            <span>{{el.name}}</span>
                            <p>{{el.achievement}}</p>
                        </div>
                    </div>
                </div>

                <div class="act-block act-events">
                    <div class="title">
                        <b class="t3"><strong class="hide">賽事活動</strong></b>
                    </div>
                    <div class="cnts">
                        <div class="act-slider">
                            <div class="swiper-container gallery">
                                <div class="swiper-wrapper">
                                    <div class="swiper-slide" ms-repeat="curAct">
                                        <a href="javascript:" ms-click="openUrl(el.link)">
                                            <img ms-attr-src="el.pic" width="255"/>
                                        </a>
                                    </div>
                                </div>
                            </div>
                            <div class="swiper-pagination"></div>
                        </div>
                    </div>
                </div>

                <div class="act-block act-god">
                    <div class="title">
                        <b class="t4"><strong class="hide">大神助力</strong></b>
                        <a href="javascript:" ms-click="openUrl(curGodLink.link)">大神名錄>></a>
                    </div>
                    <div class="cnts">
                        <div class="list">
                            <dl>
                                <dd ms-repeat="curQuot" ms-click="openUrl(el.link)">
                                    <div class="avatar"><img ms-attr-src="el.pic" width="51"/></div>
                                    <div class="info">
                                        <b>{{el.name}}</b>
                                        <p>{{el.content}}</p>
                                    </div>
                                </dd>
                            </dl>
                        </div>
                    </div>
                </div>

                <div class="act-video">
                    <div class="nav">
                        <a href="javascript:" ms-class="cur:videoIndex==0" ms-click="switchVideoTab(0, 1)">賽事視頻</a>
                        <a href="javascript:" ms-class="cur:videoIndex==1" ms-click="switchVideoTab(1, 2)">精彩集錦</a>
                        <a href="javascript:" ms-class="cur:videoIndex==2" ms-click="switchVideoTab(2, 3)">大神視角</a>
                    </div>
                    <div class="cnts">
                        <div class="cnt">
                            <a href="javascript:" ms-repeat="curVideo" ms-click="openUrl(el.link)">
                                <img ms-attr-src="el.pic" width="123"/>
                                <p>{{el.content}}</p>
                            </a>
                        </div>
                        <a href="javascript:" class="act-more" ms-click="openUrl(videoMore)">更多視頻>></a>
                    </div>
                </div>
            </div>
            <div class="act-footer">
                <div class="act-btn-group">
                    <a href="javascript:" ms-click="openUrl(btLink.more_game)">更多賽事</a>
                    <a href="javascript:" ms-click="openUrl(btLink.gamecenter)">游戲中心</a>
                </div>
                <p class="game-info">手機QQ游戲中心出品</p>
            </div>
        </div>
    </div>

    <div class="act-bg">
        <img src="http://imgcache.gtimg.cn/vipstyle/game/act/breezefeng/20151204_qgc/img/bg_01.jpg" width="320"/>
        <img src="http://imgcache.gtimg.cn/vipstyle/game/act/breezefeng/20151204_qgc/img/bg_02.jpg" width="320"/>
        <img src="http://imgcache.gtimg.cn/vipstyle/game/act/breezefeng/20151204_qgc/img/bg_03.jpg" width="320"/>
    </div>
</div>

<script src="http://imgcache.gtimg.cn/c/=/club/mobile_web/zepto.min.js,/club/common/lib/zero/zero.m.min-5.1.1.js,/club/common/lib/avalon.js?max_age=86400000"></script>

 js代碼:

new qv.zero.Page({
    jsonid: '76172',
    mqqEnv: true,
    game: 'cfm',
    onlyMobile: true,
    isOpenSQView: true,
    redirectUrl: "",
    preloads: ['mqqShare'],
    afterInit: function () {
        var me = this;

        qv.zero.Login.ensure();

        qv.zero.mqqShare.initShare();

        me.defineModel();
        me.getData();
        me.initData();
    },
    defineModel: function () {
        var me = this;
        main = avalon.define('main', function (vm) {
            //當前游戲appid
            vm.appid = 1104067326;
            //當前游戲tab索引
            vm.index = 0;
            //切換游戲tab索引
            vm.switchTab = function (index, appid) {
                main.index = index;
                main.appid = appid;
                me.changeData(appid);
            };

            //當前視頻tab索引
            vm.videoIndex = 0;
            //當前視頻類型
            vm.videoType = 1;
            //切換視頻tab索引
            vm.switchVideoTab = function (videoIndex, videoType) {
                main.videoIndex = videoIndex;
                main.videoType = videoType;
                console.log(me.getVideoByType(main.$videos[main.appid] || []));
                main.curVideo = me.getVideoByType(main.$videos[main.appid] || []);
                main.videoMore = main.curVideo[0] && main.curVideo[0].more_link || '';
            };

            //打開鏈接
            vm.openUrl = function (url) {
                me.openUrl(url);
            };

            //當前游戲
            vm.curGames = [];

            //賽事直播連接
            vm.$onlineLinks = {};
            vm.curOnlineLink = {};

            //賽事新聞
            vm.$news = {};
            vm.curNews = [];

            //賽事數據的更多數據
            vm.$dataMoreLinks = {};
            vm.curDataMoreLink = {};

            //賽事數據
            vm.$data = {};
            vm.curData = [];

            //賽事活動
            vm.$act = {};
            vm.curAct = [];

            //大神名錄鏈接
            vm.$godLinks = {};
            vm.curGodLink = {};

            //大神語錄
            vm.$quot = {};
            vm.curQuot = [];

            //賽事視頻
            vm.$videos = {};
            vm.curVideo = [];

            //賽事視頻更多鏈接
            vm.videoMore = '';

            //底部鏈接
            vm.btLink = {};
        });
    },
    changeData: function (appid) {
        var me = this;
        main.curOnlineLink = main.$onlineLinks[appid] && main.$onlineLinks[appid][0] || {};
        main.curNews = main.$news[appid] || [];
        main.curDataMoreLink = main.$dataMoreLinks[appid]  && main.$dataMoreLinks[appid][0] || {};
        main.curData = main.$data[appid] || [];
        main.curAct = main.$act[appid] || [];
        main.curGodLink = main.$godLinks[appid] && main.$godLinks[appid][0] || {};
        main.curQuot = main.$quot[appid] || [];
        main.curVideo = me.getVideoByType(main.$videos[appid] || []);
        main.videoMore = main.curVideo[0] && main.curVideo[0].more_link || '';

        setTimeout(function () {
            mySwiper.update();
        }, 30);
    },
    getData: function () {
        var me = this;
        //賽事直播鏈接
        main.$onlineLinks = me.getAmsData(2);
        //賽事新聞
        main.$news = me.getAmsData(3);
        //賽事數據的更多數據
        main.$dataMoreLinks = me.getAmsData(4);
        //賽事數據
        main.$data = me.getAmsData(5);
        //賽事活動
        main.$act = me.getAmsData(6);
        //大神名錄鏈接
        main.$godLinks = me.getAmsData(7);
        //大神語錄
        main.$quot = me.getAmsData(8);
        //賽事視頻
        main.$videos = me.getAmsData(9);
    },
    initData: function () {
        var me = this;
        main.curOnlineLink = main.$onlineLinks[main.appid] && main.$onlineLinks[main.appid][0] || {};
        main.curNews = main.$news[main.appid] || [];
        main.curDataMoreLink = main.$dataMoreLinks[main.appid] && main.$dataMoreLinks[main.appid][0] || {};
        main.curData = main.$data[main.appid] || [];
        main.curAct = main.$act[main.appid] || [];
        main.curGodLink = main.$godLinks[main.appid] && main.$godLinks[main.appid][0] || {};
        main.curQuot = main.$quot[main.appid] || [];
        main.btLink = zMsg.getFormData(10)[0];
        main.curVideo = me.getVideoByType(main.$videos[main.appid] || []);
        main.videoMore = main.curVideo[0] && main.curVideo[0].more_link || '';
        main.curGames = zMsg.getFormData(2);

        setTimeout(function () {
            mySwiper.update();
        }, 30);
        $('#loading').css('display', 'none');
        avalon.scan();
    },
    getVideoByType: function (videos) {
        var arr = [];
        for (var i = 0, len = videos.length; i< len; i++) {
            (videos[i].type == main.videoType) && (arr.push(videos[i]));
        }
        return arr;
    },
    getAmsData: function (id) {
        return this.convert2ObjByAppId(zMsg.getFormData(id));
    },
    convert2ObjByAppId: function (arr) {
        var i, cur, len, obj = {};
        for (i = 0, len = arr.length; i < len; i++) {
            cur = arr[i];
            (obj[cur.appid] || (obj[cur.appid] = [])).push(cur);
        }
        return obj;
    },
    setShareData: function () {
        var shareObj = zMsg.getFormData(11)[0];
        return {
            title: shareObj.title,//分享標題
            desc: shareObj.content, //分享內容
            imageUrl: shareObj.pic,    //分享圖片
            shareUrl: location.href,
            back: true//發送消息之后是否返回到web頁面
        }
    },
    //手Q打開鏈接
    openUrl: function (url, target) {
        var targetUrl = url;
        target = target || 1;
        if (typeof(mqq) != "undefined" && mqq.QQVersion != 0) {
            mqq.ui.openUrl({url: targetUrl, target: target, style: 0});
        } else {
            window.location.href = targetUrl;
        }
    }
});

乍一看代碼好多,感覺很復雜的樣子,實際上這里因為模塊比較多,所以代碼量稍微有點大,大家看得時候按模塊來看就很容易理解了。

我們看到使用avalon后代碼結構非常清晰,html結構也很清晰,基本上看不到DOM操作。這里我們會看到很多以ms-開頭的指令,比如ms-click是綁定click事件,ms-repeat是循環輸出數組列表等,相關指令可以去上面推薦的文檔內查閱,這里只是給大家如何使用avalon做一些拋磚引玉,avalon使用門檻很低,大家也可以多嘗試下,有問題歡迎隨時交流!

最后附上QGC情報站的頁面交互效果圖:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM