寫在前面
相信大家對Omi應該都不陌生了,如果還有不瞭解的同學先看看這裡。瞭解並使用Omi之後你會發現真的回不去了~~~
先簡單說一下吧,Omi就是一個可以快速開發專案的元件化框架,和vue/react一樣為了節省生產力的。想了解Omi和vue還有react區別的,上面文件有講解,或者加入群256426170,可以面對面諮詢Omi作者dnt。我這篇文章將使用Omi從0到1來完成一個移動端的專案,讓大家瞭解Omi開發的方便快捷。
開發準備:
這次我們挑選了一個日跡發現頁來作為例子開發,如果有用手機QQ的同學,應該有知道“日跡”這個專案,這次我們就挑選了一個日跡的一個發現頁,入口在手機QQ -> 動態 -> 日跡 -> 右上角發現
發現頁如下
開發一個移動端頁面和PC上開發是一樣的,首先要分析頁面劃分模組,發現頁很簡單,可以看成一個列表,然後裡面每一塊是一個item
如果不用元件化的話,ul+li是不是就可以上手幹了~但我們要告別原始社會的開發方式,採用Omi框架進行開發,下面就正式開始開發~
開發過程:
1/ 腳手架
開發一個專案(一個頁面也是一個專案),首先我們需要腳手架,腳手架可以從歷史專案中複製過來,也可以自己重新搭建。使用Omi的話就方便很多啦,我們只需要下面兩步
npm install omi-cli -g
omi-cli init [project name]複製程式碼
然後腳手架就OK了,下面簡單的看一下腳手架,瞭解一下專案結構
下面那些.babelrc/.eslintrc/package.json等就不說了
先看目錄,config是配置目錄,裡面有基礎配置和專案配置,一般我們不需要修改
tools裡面是構建相關,webpack.base.js是基礎配置,然後測試環境和生產環境的區分就靠script.js了
src是開發的目錄,也是我們程式碼所在地,開啟src再看一下
應該還是很好理解的,page是頁面,這裡面每個目錄就意味著有一個頁面。頁面的入口是目錄下的main
component是元件,元件也是以資料夾為粒度來的,裡面一定有一個js檔案,然後元件相關的資原始檔,樣式檔案也都放在js的同一目錄下,比如這樣
元件的圖片/樣式和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外掛的無縫支援版本。
準備工作都考慮完善之後我們就開始寫第一個元件了!第一個元件可以看成是整個列表的一個包裹盒,盒子裡面不僅有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();複製程式碼
就可以了!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就可以了
- 區域性css或者less檔名必須以_開頭,loader會針對進行操作,就像上面的程式碼一樣
- 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地址:
有問題的話可以留言大家一起交流~