一、需求分析
在本文实例中,我们将要完成一个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" ="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" 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" ="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" 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学习与实践
作者:景铭巴巴
链接:https://www.jianshu.com/p/91f8c95eb096