new tutorial

This commit is contained in:
张磊 2017-07-14 14:59:44 +08:00
parent 0a12e30af8
commit 69d5f971ba
1 changed files with 345 additions and 0 deletions

345
tutorial/omi-qq-story.md Normal file
View File

@ -0,0 +1,345 @@
# QQ日迹Omi实战开发从0到1
相信大家对omi应该都不陌生了如果还有不了解的同学先看看这里
[精通omi框架](https://github.com/AlloyTeam/omi/blob/master/tutorial/all.md)
先简单说一下吧omi就是一个可以快速开发项目的组件化框架和vuereact一样为了节省生产力的。想了解omi和vue还有react区别的上面文档有讲解或者加入群256426170可以面对面咨询omi作者dnt。我这篇文章将使用omi从0到1来完成一个移动端的项目让大家了解omi开发的方便快捷。
</br>
# 开发准备:
这次我们挑选了一个日迹发现页来作为例子开发如果有用手机QQ的同学应该有知道“日迹”这个项目这次我们就挑选了一个日迹的一个发现页入口在手机QQ -> 动态 -> 日迹 -> 右上角发现
发现页如下
<img src="http://www.alloyteam.com/wp-content/uploads/2017/07/1-169x300.jpg" alt="" width="169" height="300" class="alignnone size-medium wp-image-12861" />
开发一个移动端页面和PC上开发是一样的首先要分析页面划分模块发现页很简单可以看成一个列表然后里面每一块是一个item
如果不用组件化的话ul+li是不是就可以上手干了但我们要告别原始社会的开发方式采用omi框架进行开发下面就正式开始开发
</br>
# 开发过程:
</br>
## 1/ 脚手架
开发一个项目(一个页面也是一个项目)首先我们需要脚手架脚手架可以从历史项目中复制过来也可以自己重新搭建。使用omi的话就方便很多啦我们只需要下面两步
npm install omi-cli -g
omi-cli init [project name]
然后脚手架就OK了下面简单的看一下脚手架了解一下项目结构
<img src="http://www.alloyteam.com/wp-content/uploads/2017/07/2-204x300.png" alt="" width="204" height="300" class="alignnone size-medium wp-image-12862" />
下面那些.babelrc.eslintrcpackage.json等就不说了
先看目录config是配置目录里面有基础配置和项目配置一般我们不需要修改
tools里面是构建相关webpack.base.js是基础配置然后测试环境和生产环境的区分就靠script.js了
src是开发的目录也是我们代码所在地打开src再看一下
<img src="http://www.alloyteam.com/wp-content/uploads/2017/07/3.png" alt="" width="214" height="171" class="alignnone size-full wp-image-12863" />
应该还是很好理解的page是页面这里面每个目录就意味着有一个页面。页面的入口是目录下的main
component是组件组件也是以文件夹为粒度来的里面一定有一个js文件然后组件相关的资源文件样式文件也都放在js的同一目录下比如这样
<img src="http://www.alloyteam.com/wp-content/uploads/2017/07/4-193x300.png" alt="" width="193" height="300" class="alignnone size-medium wp-image-12864" />
组件的图片样式和js都有了那外面的cssimgjs呢是一些全局资源和公共方法等这样一来复用就极为方便了。
## 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](https://github.com/AlloyTeam/omi/tree/master/plugins/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对组件生命周期的控制可是非常强大的如下图
<img src="http://www.alloyteam.com/wp-content/uploads/2017/07/5-300x168.png" alt="" width="300" height="168" class="alignnone size-medium wp-image-12865" />
接着是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](https://github.com/AlloyTeam/omi/tree/master/plugins/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就可以了
2. 局部css或者less文件名必须以_开头loader会针对进行操作就像上面的代码一样
3. 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**
生产环境的代码就已经搞定了~接下来就是部署、提测、等bug、解bug、解bug、解bug、解bug、解bug……
# 结语
文章一些cgi、util相关的代码就省略掉了主要目的是讲解omi的开发。虽然是一个很小的页面不过可以看出来用omi+omi-cli开发还是很简单的哈~omi的能力当然不止这一点点我这篇文章只是抛砖引玉大家想解放生产力的话~快来使用omi吧~
在线体验地址请使用手机QQ扫描下方二维码
<img src="http://www.alloyteam.com/wp-content/uploads/2017/07/6.png" alt="" width="245" height="244" class="alignnone size-full wp-image-12866" />
github地址
[omi](https://github.com/AlloyTeam/omi)
[omi-cli](https://github.com/AlloyTeam/omi-cli)
有问题的话可以留言大家一起交流~