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地址:

有问题的话能够留言你们一块儿交流~

相关文章
相关标签/搜索