一、需求分析
在本文实例中,我们将要完成一个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" style="height: {{screenHeight}};background-color:rgba(0, 0, 0,{{opacity}})" >
<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" onclick="cancel">取消</text>
<text class="ok" onclick="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" style="height: {{screenHeight}};background-color:rgba(0, 0, 0,{{opacity}})" >
</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.nocheck
3.2.3:todolist_bottom.we程序清单和解释
<template>
<div class="container">
<text class="text" onclick="add">新增</text>
<text class="text" onclick="finish">完成</text>
<text class="text" onclick="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" onclick="check"></image>
<text class="title" style="text-decoration:{{textDecoration}}">{{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 style="height: {{screenHeight}}">
<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学习与实践