QQ日跡Omi實戰開發,從0到1

寫在前面

相信你們對Omi應該都不陌生了,若是還有不瞭解的同窗先看看這裏。瞭解並使用Omi以後你會發現真的回不去了~~~javascript

先簡單說一下吧,Omi就是一個能夠快速開發項目的組件化框架,和vue/react同樣爲了節省生產力的。想了解Omi和vue還有react區別的,上面文檔有講解,或者加入羣256426170,能夠面對面諮詢Omi做者dnt。我這篇文章將使用Omi從0到1來完成一個移動端的項目,讓你們瞭解Omi開發的方便快捷。css


開發準備:

此次咱們挑選了一個日跡發現頁來做爲例子開發,若是有用手機QQ的同窗,應該有知道「日跡」這個項目,此次咱們就挑選了一個日跡的一個發現頁,入口在手機QQ -> 動態 -> 日跡 -> 右上角發現
發現頁以下html

開發一個移動端頁面和PC上開發是同樣的,首先要分析頁面劃分模塊,發現頁很簡單,能夠當作一個列表,而後裏面每一塊是一個item
若是不用組件化的話,ul+li是否是就能夠上手幹了~但咱們要告別原始社會的開發方式,採用Omi框架進行開發,下面就正式開始開發~

vue

開發過程:


1/ 腳手架

開發一個項目(一個頁面也是一個項目),首先咱們須要腳手架,腳手架能夠從歷史項目中複製過來,也能夠本身從新搭建。使用Omi的話就方便不少啦,咱們只須要下面兩步java

npm install omi-cli -g
    omi-cli init [project name]

而後腳手架就OK了,下面簡單的看一下腳手架,瞭解一下項目結構react

下面那些.babelrc/.eslintrc/package.json等就不說了
先看目錄,config是配置目錄,裏面有基礎配置和項目配置,通常咱們不須要修改
tools裏面是構建相關,webpack.base.js是基礎配置,而後測試環境和生產環境的區分就靠script.js了jquery

src是開發的目錄,也是咱們代碼所在地,打開src再看一下webpack

應該仍是很好理解的,page是頁面,這裏面每一個目錄就意味着有一個頁面。頁面的入口是目錄下的maingit

component是組件,組件也是以文件夾爲粒度來的,裏面必定有一個js文件,而後組件相關的資源文件,樣式文件也都放在js的同一目錄下,好比這樣github

組件的圖片/樣式和js都有了,那外面的css/img/js呢?是一些全局資源和公共方法等,這樣一來複用就極爲方便了。

2/ 正式開發

首先咱們引入一下rem統一的js代碼,如今來講用rem仍是比px方便不少的,代碼以下:

;(function(win) {
        var doc = win.document;
        var docEl = doc.documentElement;
        var tid;

        function refreshRem() {
            var width = docEl.getBoundingClientRect().width;
            if (width > 540) { // 最大寬度
                width = 540;
            }
            var rem = width / 10; // 將屏幕寬度分紅10份, 1份爲1rem
            docEl.style.fontSize = rem + 'px';
        }

        win.addEventListener('resize', function() {
            clearTimeout(tid);
            tid = setTimeout(refreshRem, 300);
        }, false);
        win.addEventListener('pageshow', function(e) {
            if (e.persisted) {
                clearTimeout(tid);
                tid = setTimeout(refreshRem, 300);
            }
        }, false);

        refreshRem();

    })(window);

這樣咱們就將不一樣屏幕下的rem與px轉換統一了,視覺稿上面的px單位除以37.5就能夠了,這一步也能夠在構建的時候作

接下來咱們考慮到項目是一個長列表,說到長列表就確定離不開滾動,說到滾動就想到了安卓下局部滾動會很卡。那麼這裏能夠用全局滾動搞定麼?能夠的,由於頁面自己不復雜。
那麼複雜的情景下,必須是局部滾動的場景怎麼辦呢?AlloyTouch歡迎你~解決各種滾動問題,並且有Omi插件的無縫支持版本。

使用omi-touch

準備工做都考慮完善以後咱們就開始寫第一個組件了!第一個組件能夠當作是整個列表的一個包裹盒,盒子裏面不只有list,還有按鈕和一些其餘的玩意
先上一下代碼

import List from '../list/index';

    Omi.tag('List', List);

    class Main extends Omi.Component {
        constructor(data) {
            super(data);

            this.inTouch = false;
            this.touchXY = [];
            this.data.loadWord = '正在加載中...';
        }

        style() {
            return `
                .record {
                    position: fixed;
                    bottom: 0.533333rem;
                    left: 50%;
                    -webkit-transform: translateX(-50%);
                    transform: translateX(-50%);
                    background-image: url(${require('./img/record.png')});
                    width: 2.000000rem;
                    height: 2.000000rem;
                    background-size: 100% 100%;
                }
                .isend {
                    position: relative;
                    text-align: center;
                    margin: 0 auto;
                    margin-left: -12px;
                    padding: 12px 0;
                    font-size: 14px;
                    color: rgba(119, 119, 119, 1);
                }
            `;
        }

        render() {
            return `
            <div class="main">
                <List omi-id="list"></List>
                <div class="record" ontouchmove="handleTouchMove(this, event)" ontouchstart="handleTouchStart(this, event)" ontouchend="handleTouchEnd(this, event)"></div>
                <div class="isend">${this.data.loadWord}</div>
            </div>`;
        }
        handleTouchMove(dom, e) {
            this.inTouch = false;
        }
        handleTouchStart(dom, e) {
            this.inTouch = true;

            this.touchXY[0] = e.touches[0].screenX;
            this.touchXY[1] = e.touches[0].screenY;
        }
        handleTouchEnd(dom, e) {
            console.log(e.changedTouches[0]);

            var diffX = Math.abs(e.changedTouches[0].screenX - this.touchXY[0]);
            var diffY = Math.abs(e.changedTouches[0].screenY - this.touchXY[1]);

            if(this.inTouch && diffX < 30 && diffY < 30) {
                // handle tap event....
                this.inTouch = false;
            }
            e.preventDefault();
            
        }
    }

    export default Main;

超級簡單明瞭,constructor是組件的構造函數,也是生命週期的開始,由於咱們包裹盒的組件一直存在,因此沒有用上其餘生命週期的方法。但Omi對組件生命週期的控制但是很是強大的,以下圖

接着是style和render,這裏是用模版字符串寫css和html,很方便,但若是以爲麻煩也能夠用文件的形式,後面會說

下面三個是啥呢?是本身模擬的tap,由於移動端下onclick有300ms的延遲,因此咱們用的點擊都是模擬的。tap用語言描述就是一次點擊,咱們要保證touchend時候手指的位置不能距離touchstart的位置太遠,並且end和start期間不能觸發touchmove,這也就是本身實現tap的核心了。

若是有zepto的話自己能夠用ontap事件,沒必要本身去寫,可是我這裏沒有引入zepto,並且zepto自己是jquery相似的寫法,和框架開發仍是比較背馳的。那麼咱們就只能本身寫這麼多代碼去模擬麼??
固然不是!由於咱們有alloyfinger-omi版,咱們只須要這樣

安裝:

npm install omi-finger

使用:

import OmiFinger from 'omi-finger';
    OmiFinger.init();

使用omi-finger

就能夠了!alloytouch裏面的手勢操做omi-finger均可以用,並且用起來也超級方便!

......
    render() {
        return `
        <div class="main">
            <List omi-id="list"></List>
            <div class="record" omi-finger tap="handleTap"></div>
            <div class="isend">${this.data.loadWord}</div>
        </div>`;
    }

    handleTap() {
        // handle tap event....
    }
    ......

這樣就能夠了,這就是Omi插件體系的好處,順帶一提alloytouch也能夠像finger這樣使用~

這樣最外層的包裹組件就已經ok了,咱們來看核心的list組件。
再上代碼

class List extends Omi.Component {
        constructor(data) {
            super(data);

            this.length = 0;

            this.data.leftList = [];
            this.data.rightList = [];

        }
        style() {
            return require('./_index.less');
        }

        render() {
            return `
                <div class="wrap clear" omi-finger tap="handleTap">
                <div class="left">
                    ${
                        this.data.leftList.map((a, b) => 
                            `<Item data="data.leftList[${b}]"></Item>`
                        ).join('')
                    }
                </div>

                <div class="right">
                    ${
                        this.data.rightList.map((a, b) => 
                            `<Item data="data.rightList[${b}]"></Item>`
                        ).join('')
                    }
                </div>
            </div>`;
        }
        add(data) {
            for(let i = 0; i < data.length; i++) {
                // handle data


                if(i % 2 === 0) {
                    this.data.leftList.push(info);
                } else {
                    this.data.rightList.push(info);
                }
            }

            this.update();
        }
        handleTap(e) {

            // handle tap;

        }
        reset() {
            this.data.leftList = [];
            this.data.rightList = [];
        }
    }

首先能夠看到和main不一樣的是,這裏咱們就把css給抽離成文件的形式了,純看我的喜愛。不過有一些須要注意的地方:
**1. 全局css只須要在文件中import就能夠了

  1. 局部css或者less文件名必須以_開頭,loader會針對進行操做,就像上面的代碼同樣
  2. html抽離成文件的話須要用模版引擎的方式,上面代碼用的是ES6模版字符串,這樣的話是沒法抽離成文件的。**

omi.js默認的模版引擎是soda,若是還有喜歡ejs、mustache語法的同窗,雖然omi.js自己沒有內置該寫法,可是用omi.mustache.js卻將其默認爲內置模版引擎
具體的狀況以下:

  • omi.js 使用 sodajs 爲內置指令系統
  • omi.art.js 使用 art-template 爲內置模版引擎
  • omi.lite.js 不包含任何模板引擎
  • omi.mustache.js 使用 mustache.js爲內置模版引擎

接下來重點講的就是其中的循環生成子組件部分
循環渲染有多種方式,剛剛代碼部分用的是ES6執行map,而後獲取到數組中每個元素,渲染
咱們也可使用omi中內置的soda模版的指令方式,以下代碼也能夠實現一樣的功能

render() {
        return `
            <div class="wrap clear" omi-finger tap="handleTap">
            <div class="left">
                <Item o-repeat="item in leftList" group-data="data.leftList"></Item>
            </div>

            <div class="right">
                <Item o-repeat="item in rightList" group-data="data.rightList"></Item>
            </div>
        </div>`;
    }

咱們在add方法中進行數據的處理,這裏組件的data下面有兩個數組,分別是左右兩邊的。注意這裏add方法最後有調用一個update()方法,omi自己沒有雙向綁定,將更新的操做交給了開發者。固然若是但願雙向綁定的話也能夠引入Mobx之類的第三方庫。

list組件裏面有一個item組件,這個item組件就是最後一個啦,它須要從list中接受到本身的數據,而後將數據給展現出來
數據傳遞的方式有不少種,簡單的說一下

  • on-* 表明傳遞父組件向子組件注入的回調函數,比on-page-change="pageChangeHandler"
  • data-* 表明直接傳遞字符串
  • :data-* 表明傳遞javascript表達式,好比data-num="1" 表明傳遞數字1而非字符串,data-num="1+1"能夠傳遞2。
  • ::data-* 表明傳遞父組件的屬性,好比上面的::data-items="data.items"就表明傳遞this.data.items給子組件
  • data 表明傳遞父組件的屬性,好比data="user"表明傳遞this.user給子組件
  • :data 表明傳遞javascript表達式,好比data="{ name : 'dntzhang' , age : 18 }"表明傳遞{ name : 'dntzhang' , age : 18 }給子組件
  • group-data 表明傳遞父組件的數組一一映射到子組件

咱們採用的是第x種,而後item中就是簡單的展現啦

class Item extends Omi.Component {
        constructor(data) {
            super(data);
            console.log('data', data);
        }
        style() {
            return require('./_index.less');
        }
        render() {
            return `
                <div class="item">
                    <div class="card" vid="${this.data.vid}" shoot="${this.data.shoot}" uin="${this.data.uin}">
                        <div class="pic" style="background-image: url(${this.data.pic})"></div>
                        <div class="txt">
                            <div class="head" style="background-image: url(${this.data.head})"></div>
                            <div class="other">
                                <div class="nick" data-content='${this.data.nick}'>${this.data.nick}</div>
                                <div class="info">
                                    <span class="watch"><i></i>${this.data.watch}</span>
                                    <span class="like"><i></i>${this.data.like}</span>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            `;
        }
    }

    export default Item;

3/ 構建相關

開發過程當中咱們只須要npm start,而後就能夠專一的擼代碼了
能夠用默認的localhost:9000端口進行訪問
也能夠修改config目錄下的config.js文件,用路由的方式訪問,好比我這樣

module.exports = {
        "webserver": "//xxx.qq.com/mobile/",
        "cdn": "",
        "port": "9000",
        "route": "/mobile/"
    };

固然我這裏是有配置代理的,將xxx.qq.com/mobile指向了本地的localhost:9000
當你開發完成後,只須要運行

**npm run dist**

生產環境的代碼就已經搞定了~接下來就是部署、提測...

結語

文章一些cgi、util相關的代碼就省略掉了,主要目的是講解Omi的開發。雖然是一個很小的頁面,不過能夠看出來用omi+omi-cli開發仍是很簡單的哈!Omi的能力固然不止這一點點,我這篇文章只是拋磚引玉,你們想解放生產力的話,快來使用Omi吧~~

在線體驗地址,請使用手機QQ掃描下方二維碼

github地址:

有問題的話能夠留言你們一塊兒交流~

相關文章
相關標籤/搜索