一、需求分析
在本文实例中,我们将要完成一个ToDoList项目,将会管理里一天的生活安排。拥有以下几个功能。
新增当天行程
删除当前行程
给当天行程打标(设置为已完成)
数据本地缓存,避免记录的行程被销毁。
最终实现效果如下图所示:
二、基础知识预备
2.1:如何获取屏幕宽高
获取屏幕的高度,你就需要了解$getConfig()方法,他可以获取当前全局环境变量和配置信息。
var config = this.$getConfig();console.log(config);
输出信息如下:
你可以从中得到bundle的Url;屏幕的宽高,device的宽度总是等于750px,屏幕的高度是基于屏幕比例得到的一个高度值。如果你要针对于iOS,做一些特殊的设置,你也可以通过platform字段进行判断,因为我是在浏览器中进行调试,所以这里的platform为Web。
2.2:Weex的生命周期
<script>
module.exports = { data: {}, methods: {}, init: function () { console.log('在初始化内部变量,并且添加了事件功能后被触发');
}, created: function () { console.log('完成数据绑定之后,模板编译之前被触发');
}, ready: function () { console.log('模板已经编译并且生成了 Virtual DOM 之后被触发');
}, destroyed: function () { console.log('在页面被销毁时调用');
}
}</script>init内一般用于初始化一些内部变量,绑定一些自定义事件,这时还没有数据绑定,没有创建vdom,所以不能通过this获取到data和methods,也不能获取vdom的节点
created 完成了数据绑定 ,但还未开始编译模板,可以通过this获取data和methods,但不能获取vdom的节点
ready表示渲染完成 ,从子组件往上触发
destroyed 组件销毁,比如页面跳转,从子组件开始往上触发
2.3:组件之间通信
参考官方文档:组件之间通信
2.4:本地缓存组件的使用
参考官方文档:storage
三、开发过程
3.1:Weex开发的组件骨架
<template></template><style></style><script>
module.exports = { data: {
}, init: function () {
}, created: function () {
}, ready: function () {
}, methods:{
}, computed:{
}
}</script>template中主要是组件的引用,大体页面布局。类似于HTML。
style中主要是组件的CSS样式引用
script中主要就是js的调用。data属于绑定的数据,init、created、ready是Weex的生命周期方法。
methods中存放用户定义的一些js事件。
computed中主要是对绑定数据data部分进行预处理。
3.2:项目目录结构
todolist.we是目录的入口。
add_dialog.we是新增事件的弹出框组件。
icon.js是存放的一些js中使用的图片常量,主要包括选中和未选中的图片。
todolist_bottom.we是底部操作栏,包括新增、完成和操作事件的操作。
todolist_cell.we是list中每一个cell组件。
3.2.1:add_dialog.we程序清单和解释
<template>
<div class="container" >
<div class="dialog_container">
<text class="title">新增事件</text>
<input class="title-input" placeholder="请输入标题" type="text"
oninput="oninputForTitle"></input>
<input class="time-input" placeholder="请输入时间" type="text"
oninput="oninputForTime"></input>
<div class="bottom">
<text class="cancel" ="cancel">取消</text>
<text class="ok" ="ok">确定</text>
</div>
</div>
</div></template><style>
.container{ background-color:grey; position: fixed; width: 750px; top:0; left: 0; align-items: center; justify-content: center;
} .dialog_container{ background-color: white; width: 600px; height: 310px; align-items: center;
} .title{ margin-top: 20px; font-size:40px;
} .title-input{ margin-top: 20px; height: 60px; width: 500px; font-size: 25px; border-width: 1px;
} .time-input{ margin-top: 20px; height: 60px; width: 500px; font-size: 25px; border-width: 1px;
} .bottom{ margin-top: 30px; flex-direction: row; width: 400;
} .cancel{ text-align: center; flex: 1;
} .ok{ text-align: center; flex: 1;
}</style><script>
module.exports = { data: { screenHeight:0, opacity:0.5, title: '', time: ''
}, created: function () { var config = this.$getConfig(); var env = config.env; var screenHeight = env.deviceHeight; this.screenHeight = screenHeight;
}, methods:{ cancel: function () { this.$dispatch('dialog-cancel');
}, ok: function () { this.$dispatch('dialog-ok',{ title : this.title, time : this.time
});
}, oninputForTitle: function(event) { this.title = event.value;
}, oninputForTime : function (event) { this.time = event.value;
}
}
}</script>弹出层的背景是一个半透明的阴影部分,高度要和屏幕高度一致,所以我们需要通过this.$getConfig().env.deviceHeight获取高度之后,设置给最外层的style属性。
<div class="container" ></div>
input组件中如果我们要获取字体改变的事件,并且获取当前的text内容的话,我们需要监听oninput事件,然后在该事件中进行数据绑定。
oninputForTitle: function(event) { this.title = event.value;
},oninputForTime : function (event) { this.time = event.value;
}点击确定和取消的时间需要通过This.$dispatch()方法传递给父组件
cancel: function () { this.$dispatch('dialog-cancel');
},ok: function () { this.$dispatch('dialog-ok',{ title : this.title, time : this.time
});
},3.2.2:icon.js程序清单和解释
module.exports = {
check: "http://p1.bpimg.com/567571/1a7a707567b04e4e.png",
nocheck : "http://p1.bpimg.com/567571/b3e63a0c308955f0.png"}其他文件中可以通过以下方式进行引用
const icon = require('./icon');
icon.check
icon.nocheck3.2.3:todolist_bottom.we程序清单和解释
<template>
<div class="container">
<text class="text" ="add">新增</text>
<text class="text" ="finish">完成</text>
<text class="text" ="delete">删除</text>
</div></template><style>
.container{ background-color: aquamarine; height: 200px; flex-direction: row; align-items: center;
} .text{ flex: 1; text-align: center; padding: 40px;
}</style><script>
module.exports = {
data : {
},
methods : {
add : function () { this.$dispatch('bottom-add');
},
finish : function () { this.$dispatch('bottom-finish');
}, delete : function () { this.$dispatch('bottom-delete');
}
}
}</script>添加、完成和删除方法都通过this.$dispatch方法把事件传递给父组件进行了调用。
3.2.4:todolist_cell.we程序清单和解释
<template>
<div class="container">
<image src="{{picUrl}}" class="pic" ="check"></image>
<text class="title" >{{item.title}}</text>
<text class="time">{{item.time}}</text>
</div></template><style>
.container{ background-color:gainsboro; height: 80px; margin: 10px; flex-direction: row; align-items: center;
} .pic{ width: 40px; height: 40px; margin-left: 20px;
} .title{ margin-left: 20px; flex: 1;
} .time{ margin-right: 20px;
}</style><script>
const icon = require('./icon');
module.exports = {
data : {
item:[]
},
methods : {
check : function () { this.item.check = !this.item.check;
}
},
computed: {
picUrl: { get : function () { if(this.item){ var check = this.item.check; if(check){ return icon.check;
}else{ return icon.nocheck;
}
}
}
},
textDecoration: { get : function () { if(this.item){ var finish = this.item.finish; if(finish){ return 'line-through';
}else{ return 'none';
}
}
}
}
}
}</script>父组件中通过以下方式切入子组件
<todolist_cell item="{{item}}"></todolist_cell>需要在子组件的data区域中也要定义一个item属性,我们才能接收到父组件传给子组件的值。
对显示的数据进行预处理。因为每一cell种图片是否选中item中只有一个check字段与之对应,而check字段只是bool,并不是一个check图片的picUrl。picUrl不在data区域,而是根据data区域的值的逻辑获得,所以我们可以在computed区域中对其的get方法进行操作。
computed: { picUrl: { get : function () { if(this.item){ var check = this.item.check; if(check){ return icon.check;
}else{ return icon.nocheck;
}
}
}
}, textDecoration: { get : function () { if(this.item){ var finish = this.item.finish; if(finish){ return 'line-through';
}else{ return 'none';
}
}
}
}
}3.2.5:todolist.we程序清单和解释
<template>
<div >
<div class="list-container">
<list>
<cell repeat="{{item in items}}">
<todolist_cell item="{{item}}"></todolist_cell>
</cell>
</list>
</div>
<todolist_bottom></todolist_bottom>
<add_dialog if="{{showDialog}}"></add_dialog>
</div></template><style>
.list-container{ background-color: bisque; flex: 1;
}</style><script>
//require('weex-components');
require('./component/todolist_bottom.we'); require('./component/todolist_cell.we'); require('./component/add_dialog.we'); var modal = require('@weex-module/modal'); var storage = require('@weex-module/storage');
module.exports = { data: { platform: 'Web', screenHeight : 0, showDialog: false, items : []
}, init: function () {
}, created: function () { var config = this.$getConfig(); console.log(JSON.stringify(config)); var env = config.env; var screenHeight = env.deviceHeight; this.screenHeight = screenHeight; this.platform = env.platform; if (this.platform && this.platform === 'Web'){
}else{ this.screenHeight = screenHeight - 120;
} var self = this;
storage.getItem('todolist_cache', function(e) { var result = e.result; if (result === 'success') { var data = e.data;
self.items = JSON.parse(data);
} else {
}
}); var self = this; this.$on('bottom-add', function () {
self.showDialog = true;
}); this.$on('bottom-finish', function () { for(var index in this.items){ var item = this.items[index]; var check = item.check; if(check){
item.finish = true;
item.check = false;
}
}
storage.setItem('todolist_cache', JSON.stringify(this.items), function(e) {
});
}); this.$on('bottom-delete', function () { var tempArray = []; for(var index in this.items){ var item = this.items[index]; var check = item.check; if(!check){
tempArray.push(item);
}
} this.items = tempArray;
storage.setItem('todolist_cache', JSON.stringify(this.items), function(e) {
});
}); this.$on('dialog-cancel', function () { this.showDialog = false;
}); this.$on('dialog-ok',function (e) { this.showDialog = false; var detail = e.detail; var title = detail.title; var time = detail.time; this.items.push({ title:title, time:time, check:false, finish:false
});
storage.setItem('todolist_cache', JSON.stringify(this.items), function(e) {
});
});
}, ready: function () {
}, methods:{
},
}</script>list组件的使用
<div class="list-container">
<list>
<cell repeat="{{item in items}}">
<todolist_cell item="{{item}}"></todolist_cell>
</cell>
</list></div>引用自定义组件的系统自带组件
require('./component/todolist_bottom.we'); require('./component/todolist_cell.we'); require('./component/add_dialog.we'); var modal = require('@weex-module/modal');//系统自带toast组件
var storage = require('@weex-module/storage');//系统自带本地数据缓存组件数据缓存
获取数据,注意这里需要保存this对象给self,应该在回调方法中,this对象已经不是该类自己了。
var self = this;
storage.getItem('todolist_cache', function(e) { var result = e.result; if (result === 'success') { var data = e.data;
self.items = JSON.parse(data);
} else {
}
});保存数据
storage.setItem('todolist_cache', JSON.stringify(this.items), function(e) {
});父组件中注册监听子组件中传递过来的事件
this.$on('bottom-add', function () {
});this.$on('bottom-finish', function () {
});this.$on('bottom-delete', function () {
});this.$on('dialog-cancel', function () {
});this.$on('dialog-ok',function (e) {
});四、总结
本文实现一个简单的todolist实例,大家对于Weex的基本使用应该有了更深的了解。而且这个Demo虽然简单,但是也包括了不少内容,包括父子组件的通信、weex中本地缓存的使用、如何自定义组件、commonJS的封装等等。有时候在使用weex的时候发现,flex布局没有效果,这时候你试着给该组件设置height就会解决问题。
如果你在weex todolist.we 或者 weex todolist.we --qr报错的话
yixiangdeMacBook-Pro:ToDoList yixiang$ weex todolist.we ERR! ModuleNotFoundError: Module not found: Error: Cannot resolve module 'babel-runtime/core-js/json/stringify' in /Users/yixiang/Weex/Demo/ToDoList info Please try to enter directory where your we file saved, and run command 'npm install babel-runtime' yixiangdeMacBook-Pro:ToDoList yixiang$ weex todolist.we --qr ERR! ModuleNotFoundError: Module not found: Error: Cannot resolve module 'babel-runtime/core-js/json/stringify' in /Users/yixiang/Weex/Demo/ToDoList info Please try to enter directory where your we file saved, and run command 'npm install babel-runtime'
尝试根据提示npm install babel-runtime,应该就会解决问题。
五、参考资料
Weex Github仓库
Weex 英文文档(比较全)
Weex 中文文档(非官方提供,不全)
Weex学习与实践
作者:景铭巴巴
链接:https://www.jianshu.com/p/91f8c95eb096