上一篇文章我们学习了<list>、<cell>、<loading>和<refresh>组件,这都都是在项目中常用的。那这篇文章我们一起来学习<recycle-list>、<scroller>、<slider>、<indicator>、<textarea>和<input>,大家可能会问为什么Weex的组件为什么要一个一个学下来呢?其实这是一个很普遍的问题,任何技术不打好基础,以后实际运用的时候是要买单的,还是那句话,基础不牢,地动山摇。
那么我们首先来看一下<recycle-list>组件,这个组件是不是和我们Android中的RecycleView很像,其实他的功能就是一样的。
1.<recycle-list>的简介和使用
<recycle-list>
是一个新的列表容器,具有回收和复用的能力,可以大幅优化内存占用和渲染性能。它只能使用 <cell-slot>
作为其直接子节点,使用其他节点无效。
<cell-slot>
代表的是列表每一项的模板,它只用来描述模板的结构,并不对应实际的节点。<cell-slot>
的个数只表示模板的种类数,真实列表项的个数是由数据决定的。
属性 | 说明 |
---|---|
case | 声明了当前模板的类型,只有和数据中的类型与当前类型匹配时才会渲染,语义和编程语言里的 case 一致。 |
所有模板中最多只会匹配到一项,按照模板的顺序从上到下匹配,一旦匹配成功就不在继续匹配下一个。 | |
default | 表示当前模板为默认模板类型,不需要指定值。如果数据项没有匹配到任何 case 类型,则渲染带有 default 模板。如果存在多个 default ,则只会使用第一个默认模板。 |
key | 可选属性,用于指定列表数据中可以作为唯一标识的键值,可以优化渲染性能。 |
<recycle-list>的属性如下:
-
for在
<recycle-list>
添加for
属性即可描述如何循环展开列表的数据,语法和 Vue 的v-for
指令类似,但是它循环的是自己内部的子节点,并不是当前节点。写法:alias in expression
(alias, index) in expression
-
switch
在<recycle-list>
添加switch
属性可以用来指定数据中用于区分子模板类型的字段名,语义和编程语言里的 switch 一致,配合<cell-slot>
中的case
和default
属性一起使用。
如果省略了switch
属性,则只会将第一个<cell-slot>
视为模板,多余的模板将会被忽略。
简单的使用示例如下:
<recycle-list for="(item, i) in longList" switch="type">
<cell-slot case="A">
<text>- A {{i}} -</text>
</cell-slot>
<cell-slot case="B">
<text>- B {{i}} -</text>
</cell-slot>
</recycle-list>
在 <recycle-list>
中使用的子组件也将被视为模板,在开发组件时给 <template>
标签添加 recyclable 属性,才可以用在 <recycle-list>
中。
<template recyclable>
<div>
<text>...</text>
</div>
</template>
<script>
// ...
</script>
特别说明的是:添加了 recyclable
属性并不会影响组件本身的功能,它仍然可以用在其他正常的组件里。
使用<recycle-list>需要注意以下几点:
1. 属性和文本的绑定
绑定属性或者文本时,仅支持表达式,不支持函数调用,也不支持使用 filter,可以参考 Implementation.md#支持的表达式,例如,下列写法不可用:
<div :prop="capitalize(card.title)">
<text>{{ card.title | capitalize }}</text>
</div>
针对这种场景,推荐使用 computed属性来实现。因为模板的取值是由客户端实现的,而函数的定义在前端(filter 可以认为是在模板里调用函数的语法糖),如果每次取值都走一次通信的话,会大幅降低渲染性能
<slot>
不可用<cell-slot>
的功能和 <slot>
有部分重叠,而且更为激进,在概念上有冲突,存在很多边界情况无法完全支持。不要在 <cell-slot>
及其子组件里使用 <slot>
- v-once 不会优化渲染性能
和前端框架中的理解不同,客户端里要实现复用的逻辑,会标记模板节点的状态,添加了 v-once
能保证节点只渲染一次,但是并不一定能优化渲染性能,反而可能会拖慢客户端复用节点时的比对效率。
3. 样式功能的限制
目前版本里还不支持绑定样式类名(v-bind:class
),后续会支持的
4. 子组件的限制
-
没有 Virtual DOM! 使用在
<recycle-list>
中的组件没有 Virtual DOM!与 Virtual DOM 相关的功能也不支持。在开发过程中尽量只处理数据,不要操作生成后的节点。下列这些属性都不再有意义,请不要使用:
vm.$refs
里的值可能是数组、子组件的实例、DOM 元素,在前端里比较常用,如果不支持,对 Weex 里的dom
模块和animation
模块的功能也有影响。目前正在讨论技术方案,部分接口可能会重新设计,或者是在
vm
上透出专为<recycle-list>
设计的接口。vm.$el
vm.$refs.xxx
vm.$vnode
vm.#slots
vm.#scopedSlots
-
组件的属性 目前子组件的属性不支持函数。(正在讨论实现方案)
<sub-component :prop="item.xxx" />
因为子组件的属性值需要在前端和客户端之间传递,所以仅支持可序列化的值。
item.xxx
的类型可以是对象、数组、字符串、数字、布尔值等,不支持函数。 -
生命周期的行为差异 由于列表的渲染存在回收机制,节点渲染与否也与用户的滚动行为有关,组件的生命周期行为会有一些不一致。
可回收长列表不会立即渲染所有节点,只有即将滚动到可视区域(以及可滚动的安全区域)内时才开始渲染,组件生命周期的语义没变,但是会延迟触发。
假设有 100 条数据,一条数据了对应一个组件。渲染首屏时只能展示 8 条数据的节点,那就只有前 8 个组件被创建了,也只有前 8 个组件的生命周期被触发。
- 组件的
beforeCreate
和created
也只有在组件即将创建和创建完成时才会触发 - 同理,组件的
beforeMount
和mounted
也只有页面真正渲染到了该组件,在即将挂载和已经挂载时才会触发
- 组件的
组件的自定义事件
计划支持。vm.$on
,vm.$once
,vm.$emit
,vm.$off
等功能还未完全调通,接口可用,但是行为可能有些差异(参数丢失),暂时不要使用。
使用示例如下:
<recycle-list for="(item, i) in longList" switch="type">
<cell-slot case="A">
<text>- A {{i}} -</text>
</cell-slot>
<cell-slot case="B">
<text>- B {{i}} -</text>
</cell-slot>
</recycle-list>
如果有如下数据:
const longList = [
{ type: 'A' },
{ type: 'B' },
{ type: 'B' },
{ type: 'A' },
{ type: 'B' }
]
则会生成如下等价节点:
<text>- A 0 -</text>
<text>- B 1 -</text>
<text>- B 2 -</text>
<text>- A 3 -</text>
<text>- B 4 -</text>
如果将模板合并成一个,也可以省略 switch
和 case
,将例子进一步简化:
<recycle-list for="(item, i) in longList">
<cell-slot>
<text>- {{item.type}} {{i}} -</text>
</cell-slot>
</recycle-list>
2.<scroller>的简介和使用
<scroller>
是一个容纳子组件进行横向或竖向滚动的容器组件。如果你的组件需要进行滚动,可以将 <scroller>
当作根元素或者父元素使用,否则页面无法滚动( <list>
组件除外, <list>
默认可以滚动)。
有几点需要开发者特别的注意:
- 不允许相同方向的
<list>
或者<scroller>
互相嵌套,换句话说就是嵌套的<list>
/<scroller>
必须是不同的方向。 -
<scroller>
需要显式的设置其宽高,可使用position: absolute;
定位或width
、height
设置其宽高值。
<template>
<scroller class="scroller">
<div class="row" v-for="row in rows" :key="row.id">
<text class="text">{{row.name}}</text>
</div>
</scroller>
</template>
<script>
const dom = weex.requireModule('dom')
export default {
data () {
return {
rows: []
}
},
created () {
for (let i = 0; i < 80; i++) {
this.rows.push({id: i, name: 'row ' + i})
}
},
}
</script>
<scroller> 支持任意类型的 Weex 组件作为其子组件。 其中,还支持以下两个特殊组件作为子组件:
<scroller>的属性
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
show-scrollbar | 控制是否出现滚动条 | boolean | true |
scroll-direction | 控制滚动的方向 | string(horizontal 或者 vertical) | vertical |
loadmoreoffset | 触发 loadmore 事件所需要的垂直偏移距离(设备屏幕底部与页面底部之间的距离)。当页面的滚动条滚动到足够接近页面底部时将会触发 loadmore 这个事件 |
number | 0 |
offset-accuracy | 控制 scroll 事件触发的频率,默认值为 10,表示两次 scroll 事件之间列表至少滚动了 10px。注意,将该值设置为较小的数值会提高滚动事件采样的精度,但同时也会降低页面的性能 |
number | 10 |
scrollToBegin | 控制 scroll 内容(layout)改变后,是否自动滚到初时位置。默认是true |
string | true |
特别提醒:
scroll-direction
定义了 scroller 的滚动方向,样式表属性 flex-direction
定义了 scroller 的布局方向,两个方向必须一致。例如:
-
scroll-direction
的默认值是vertical
,flex-direction
的默认值是column
; - 当需要一个水平方向的 scroller 时,使用
scroll-direction="horizontal"
和flex-direction: row
; - 当需要一个竖直方向的 scroller 时,使用
scroll-direction="vertical"
和flex-direction: column
,由于这两个值均是默认值,当需要一个竖直方向的 scroller 时,这两个值可以不设置。
<scroller>有如下四个属性:
loadmore
:如果滚动到底部将会立即触发这个事件,你可以在这个事件的处理函数中加载下一页的列表项,可通过loadmoreoffset
属性设置触发偏移距离。-
scroll
:列表发生滚动时将会触发该事件,事件的默认触发频率为 10px,即列表每滚动 10px 触发一次,可通过属性offset-accuracy
设置抽样率。事件中Event
对象有以下属性:属性 说明 类型 contentSize
列表的内容尺寸 Object width
列表内容宽度 number height
列表内容高度 number contentOffset
列表的偏移尺寸 Object x
x 轴上的偏移量 number y
y 轴上的偏移量 number scrollstart
:其中H5 暂不支持该事件,当列表开始滚动时触发,当前的内容高度和列表偏移会在 callback 中返回。scrollend
:其中H5 暂不支持该事件,与scrollstar
类似,当列表结束滚动时触发,当前的内容高度和列表偏移会在 callback 中返回。
3.<slider>的简介和使用
Slider 组件用于在一个页面中展示多个图片,在前端这种效果被称为轮播图。默认的轮播间隔为3秒。它支持任意类型的 Weex 组件作为其子组件。你也可以放置一个 indicator
组件用于显示轮播指示器。indicator
也只能作为 Slider
的子组件使用。indicator
不能再包含其它子组件了。
<slider>的属性有如下几种:
- auto-play, boolean. 组件渲染完成时,是否自动开始播放,默认为 false.
- interval, number(ms). 轮播间隔,默认为 3000ms。
- index, number. 设置显示slider的第几个页面。
-
offset-x-accuracy, number. 控制
onscroll
事件触发的频率,默认值为10,表示两次onscroll
事件之间滚动容器至少滚动了10px。将该值设置为较小的数值会提高滚动事件采样的精度,但同时也会降低页面的性能。 - show-indicators, boolean. 是否显示指示器。
- infinite, boolean. 设置是否可以无限轮播,默认为 true。
- scrollable, boolean. 设置是否可以通过滑动手势来切换页面,默认为 true。
- keep-index, boolean, Android. 设置轮播器中的数据发生变化后是否保持变化前的页面序号。
- forbid-slide-animation, boolean, v0.20+ & iOS. iOS 平台默认支持动画,使用该属性可以强制关闭切换时的动画。
简单的使用示例如下所示:
<template>
<div>
<slider class="slider" interval="3000" auto-play="true">
<div class="frame" v-for="img in imageList">
<image class="image" resize="cover" :src="img.src"></image>
</div>
</slider>
</div>
</template>
<style scoped>
.image {
width: 700px;
height: 700px;
}
.slider {
margin-top: 25px;
margin-left: 25px;
width: 700px;
height: 700px;
border-width: 2px;
border-style: solid;
border-color: #41B883;
}
.frame {
width: 700px;
height: 700px;
position: relative;
}
</style>
<script>
export default {
data () {
return {
imageList: [
{ src: 'https://gd2.alicdn.com/bao/uploaded/i2/T14H1LFwBcXXXXXXXX_!!0-item_pic.jpg'},
{ src: 'https://gd1.alicdn.com/bao/uploaded/i1/TB1PXJCJFXXXXciXFXXXXXXXXXX_!!0-item_pic.jpg'},
{ src: 'https://gd3.alicdn.com/bao/uploaded/i3/TB1x6hYLXXXXXazXVXXXXXXXXXX_!!0-item_pic.jpg'}
]
}
}
}
</script>
4.<indicator>的属性和使用
indicator
只能在 slider
中使用,说白了就是轮播图底部的小点,不可添加任何子元素,其中它的样式也很简单:
- item-color, color. 指示点默认颜色(未选中态)。
- item-selected-color, color. 指示点选中颜色。
- item-size, number. 指示点半径。
5.<input>的属性和使用
Weex 内置的 <input>
组件用来创建接收用户输入字符的输入组件。 <input>
组件的工作方式因 type
属性的值而异,比如 text
, password
,url
,email
,tel
等。值得注意的是,此组件不支持 click
事件。请监听 input
或 change
来代替 click
事件,同时也不支持任何子组件。
其属性可见表格:
key | 类型 | 描述 | 默认值 | 备注 |
---|---|---|---|---|
type |
string | 控件的类型 | text | type 值可以是 text,date,datetime,email, password,tel,time,url,number 。每个 type 值都符合 W3C 标准。 |
其中,date 和 time 会使用系统默认组件(Android + IOS)查看示例 |
value
| string | 组件的默认内容 |
placeholder
| string | 提示用户可以输入什么。 提示文本不能有回车或换行
autofocus
| boolean | 布尔类型的数据,表示是否在页面加载时控件自动获得输入焦点
maxlength
| nubmer | 一个数值类型的值,表示输入的最大长度
return-key-type
| string | 键盘返回键的类型(即手机输入法右下角回车按钮的地方)
支持 defalut;go;next;search;send,done
查看示例
singleline
| boolean | 控制内容是否只允许单行 | true |max-length
| number | 控制输入内容的最大长度字符串长度,即中英文字符长度都为 1
max
| string | 控制当 type 属性为 date 时选择日期的最大时间,格式为 yyyy-MM-ddmin
| string | 控制当 type 属性为 date 时选择日期的最小时间,格式为 yyyy-MM-ddupriseOffset
v0.21+ & iOS | number | 当键盘弹起可能盖住输入框时,页面整体会上移。这个属性指定键盘上边缘与输入框下边缘的间隙。使用 iOS 系统坐标,默认是 20。hideDoneButton
iOS | number | 隐藏键盘上面的完成栏 | false |
其中 placeholder-color {color},placeholder 字符颜色。默认值是 #999999
<input>组件方法:
focus() 将 input
组件聚焦。
blur() 从 input
组件中移除焦点并关闭软键盘(如果它具有焦点)。
setSelectionRange(selectionStart, selectionEnd) ,设置文本选区
key | 类型 | 描述 |
---|---|---|
selectionStart | number | 设置文本选区的起始点 |
selectionEnd | number | 设置文本选区的起终点 |
getSelectionRange(function(params){}) ,获取文本选区
key | 类型 | 描述 |
---|---|---|
params.selectionStart | number | 文本选区的起始点 |
params.selectionEnd | number | 文本选区的起终点 |
setTextFormatter(params) ,这是一个非常有用的特性,可以对 input 设置一组对输入的内容进行实时格式化的规则
input标签使用及事件处理demo:
<template>
<div>
<div class="title">
<text style="text-align: center;font-size: 50px;"><input> Component Demo</text>
</div>
<div class="output">
<scroller>
<text>
{{state}} {{keyboard_state}}
</text>
</scroller>
</div>
<div>
<input test-id="input-obj" ref="range" class="input" type="text" placeholder="input placeholder" @input="eventInput" @change="onChange" @focus="onFocus" @blur="onBlur" @keyboard="onKeyBoard"></input>
</div>
<div class="group">
<div class="panel"><text class="text" @click="setRange">setSelectionRange</text></div>
<div class="panel"><text class="text" @click="getRange">getSelectionRange</text></div>
</div>
</div>
</template>
<script>
module.exports = {
data : {
state:'',
keyboard_state: ''
},
methods : {
eventInput:function (e) {
this.state='input: ' + JSON.stringify(e)
},
onChange:function(e){
this.state='change: '+ JSON.stringify(e)
},
onFocus:function (e) {
this.state='focus: '+ JSON.stringify(e)
},
onBlur:function (e) {
this.state='blur: '+ JSON.stringify(e)
},
onKeyBoard: function(e) {
this.keyboard_state = "\n onkeyboard: " + JSON.stringify(e)
},
setRange() {
this.$refs.range.setSelectionRange(1, 4);
},
getRange() {
this.$refs.range.getSelectionRange(params => {
this.state = `当前文本选区为 ${params.selectionStart} ~ ${params.selectionEnd}`;
this.keyboard_state = "";
});
},
}
}
</script>
<style scoped>
.mr-base{
margin: 10px;
}
.title {
height: 100px;
margin-top: 30px;
margin-bottom: 20px;
flex-direction: column;
justify-content: center;
}
.output {
border-width: 2px;
border-style: solid;
border-color: rgb(162, 217, 192);
width: 650px;
height: 200px;
margin-left: 50px;
margin-top: 20px;
margin-bottom: 30px;
}
.desc{
color:#aa0000;
font-size: 30px;
}
.input{
width: 650px;
height:100px;
border-width: 1px;
border-style: solid;
border-color: rgb(162, 217, 192);
placeholder-color: #41B883;
margin-left: 50px;
padding-left: 20px;
font-size: 28px;
}
.panel {
height: 100px;
flex-direction: column;
border-width: 2px;
border-style: solid;
border-color: rgb(162, 217, 192);
background-color: rgba(162, 217, 192, 0.2);
margin-left: 20px;
display:inline-block;
padding: 10px;
padding-top: 20px;
}
.group {
flex-direction: row;
width:auto;
margin-top: 30px;
margin-left: 45px;
margin-right: auto;
}
</style>
6.<textarea>的属性和使用
<textarea>
与 <input>
组件类似,可用于接受用户输入数据。<textarea>
支持多行文本输入。 <textarea>
支持 <input>
支持的所有的属性、样式和事件,不支持子组件。
除了支持 input
支持的所有属性外,textarea
还支持 row
属性,用于指定输入的行数。
- row, number, 默认值为2。
<textarea>的事件:
- 通用事件 支持所有通用事件。
-
input. 当输入状态时,会不断触发。
- @param value: 当前文本。
-
change. 当用户完成了输入时触发。
- @param value: 当前文本。
- focus. 当输入框获得焦点时触发。
- blur. 当输入框失去焦点时触发。
-
return. 当用户点击了“回车”按钮时触发,会返回此时“回车”按钮的动作类型。
- @param value: 当前文本。
- @param returnKeyType, "default" | "go" | "next" | "search" | "send" | "done".
-
keyboard. 当键盘弹起或收起时触发。
- @param isShow: boolean, 显示或隐藏键盘。
- @param keyboardSize: 键盘的高度,以前端使用的样式单位返回。
其示例:
<template>
<div class="wrapper">
<textarea class="textarea" @input="oninput" @change="onchange" @focus="onfocus" @blur="onblur"></textarea>
</div>
</template>
<script>
const modal = weex.requireModule('modal')
export default {
methods: {
oninput (event) {
console.log('oninput:', event.value)
modal.toast({
message: `oninput: ${event.value}`,
duration: 0.8
})
},
onchange (event) {
console.log('onchange:', event.value)
modal.toast({
message: `onchange: ${event.value}`,
duration: 0.8
})
},
onfocus (event) {
console.log('onfocus:', event.value)
modal.toast({
message: `onfocus: ${event.value}`,
duration: 0.8
})
},
onblur (event) {
console.log('onblur:', event.value)
modal.toast({
message: `input blur: ${event.value}`,
duration: 0.8
})
}
}
}
</script>
<style>
.textarea {
font-size: 50px;
width: 650px;
margin-top: 50px;
margin-left: 50px;
padding-top: 20px;
padding-bottom: 20px;
padding-left: 20px;
padding-right: 20px;
color: #666666;
border-width: 2px;
border-style: solid;
border-color: #41B883;
}
</style>
至此<recycle-list>、<scroller>、<slider>、<indicator>、<textarea>和<input>这六个组件就学习完了,学起来是不是觉得和Android的组件极度相似?所以在学起来是没有任何难度的。那么下篇博客我们将学习Weex剩下的四个常用组件,不积跬步无以至千里,新技术的路上,加油!