在chameleon项目中我们完成一个跨端组件平常有两种思绪:运用第三方组件封装与基于chameleon语法一致完成。
在《编写chameleon跨端组件的准确姿态(上篇)》中, 我们引见了怎样运用第三方库封装跨端组件,然则绝大多数组件并不需要那样差别化完成,绝大多数情况下我们引荐运用chameleon语法一致完成跨端组件。本篇是编写chameleon跨端组件的准确姿态系列文章的下篇,与上篇给出的示例雷同,本篇也以封装一个跨端的indexlist组件为例,起首引见怎样运用chameleon语法一致完成一个跨端组件,然后对照两种组件开辟体式格局并给出开辟发起。
终究结果
以下结果依此为weex端、web端、支付宝小顺序端、微信小顺序端以及百度小顺序端:
开辟
项目初始化
建立一个新项目 cml-demo
cml init project
进入项目
cd cml-demo
组件建立
cml init component
挑选“一般组件”, 并并输入组件名字“indexlist”, 完成组件的建立, 建立以后的组件位于src/components/indexlist文件夹下。
组件设想
为了轻易申明,本例暂时完成一个具有基本功用的indexlist组件。从功用方面讲,indexlist组件重要由两部份构成,主列表地区和索引地区。在用户点击组件右边索引时,主列表能够疾速定位到对应地区;在用户滑动组件主列表时,右边索引追随滑动不断切换当前索引项。从输入输出方面讲,组件最少应当在用户挑选某一项时抛出一个onselect事宜,通报用户当前所选中项的数据;最少应当接收一个datalist,作为其衬着的数据源,这个datalist应当是一个类似于以下构造的对象数组:
const dataList = [
{
name: '阿里',
pinYin: 'ali',
}, {
name: '北京',
pinYin: 'beijing',
},
.....
]
重要数据构造设想
依据设想的组件功用与输入输出, 我们最先设想数据构造。
indexlist组件右边的索引列对应的数据构造为一个数组,个中的每一项示意一个索引,细致构造以下:
this.shortcut = [ 'A', 'B', 'C', ....]
indexlist组件的主列表地区对应的数据构造也是一个数组,个中的每一项示意一个子列表地区(比方以首字母a开首的子列表)。下面我们斟酌每个子列表地区中最少应当包括的字段:
- 一个name字段,示意该子列表地区的称号;
- 一个items字段,该字段也是一个数组,数组中的每一项示意该子列表地区的每一项;
- 一个offsetTop, 示意该子列表地区间隔主列表顶部的间隔,经由过程该字段完成点击右边索引时能够经由过程转动响应间隔疾速定位到该子列表;
- 一个totalHeight字段,示意该子列表地区的所占的高度,经由过程该字段与offsetTop字段能够肯定每个子列表地点的高度局限, 以此完成右边索引追随滑动不断切换当前索引项
由上面剖析可得主列表地区数据构造以下:
this.list = [
{
name: "B",
items:[
{
name: "北京",
pinYin: "beijing"
},
{
name: "包头",
pinYin: "baotou"
}
...
],
offsetTop: 190,
totalHeight: 490
},
....
]
功用完成
从前文可知,输入组件的datalist具有以下构造:
const dataList = [
{
name: '阿里',
pinYin: 'ali',
}, {
name: '北京',
pinYin: 'beijing',
},
.....
]
能够发明该datalist构造是扁平而且缺少许多信息(比方totalHeight等)的,因而起首要从输入数据中整理出来所需的数据构造,修正src/components/indexlist/indexlist.cml的js部份:
initData() {
// get shortcut
this.dataList.forEach(item => {
if (item.pinYin) {
let firstName = item.pinYin.substring(0, 1);
if (item.pinYin && this.shortcut.indexOf(firstName.toUpperCase()) === -1) {
this.shortcut.push(firstName.toUpperCase());
};
};
});
// handle input data
const cityData = this.shortcut.map(item => ({items:[], name: item}));
this.dataList.forEach((item) => {
let firstName = item.pinYin.substring(0, 1).toUpperCase();
let index = this.shortcut.indexOf(firstName);
cityData[index].items.push(item);
});
// calculate item offsetTop && totalHeight
cityData.forEach((item, index) => {
let arr = cityData.slice(0, index);
item.totalHeight = this.itemNameHeight + item.items.length * this.itemContentHeight;
item.offsetTop = arr.reduce((total, cur) => (total + this.itemNameHeight + cur.items.length * this.itemContentHeight), 0);
});
this.list = cityData;
},
如许我们就拿到了主列表数组this.list与索引列表数组this.shortcut, 然后依据数组构造编写模板内容。模板内容分为两大部份,一个是主列表地区,修正src/components/indexlist/indexlist.cml文件模板部份:
<scroller
height="{{-1}}"
class="index-list-wrapper"
scroll-top="{{offsetTop}}"
c-bind:onscroll="handleScroll"
>
<view
c-for="{{list}}"
c-for-item="listitem"
class="index-list-item"
>
<view class="index-list-item-name" style="{{compItemNameHeight}}">
<text class="index-list-item-name-text">{{listitem.name}}</text>
</view>
<view
c-for="{{listitem.items}}"
c-for-item="subitem"
class="index-list-item-content"
style="{{compItemContentHeight}}"
c-bind:tap="handleSelect(subitem)"
>
<text class="index-list-item-content-text"> {{subitem.name}}</text>
</view>
</view>
</scroller>
个中scroller是一个chameleon供应的内置转动组件,其属性值scrolltop示意当前转动的间隔,onscroll示意转动时触发的事宜。在主列表这一部份,我们要完成以下功用:
- 在转动时,右边索引不断切换当前索引项的功用
- 点击列表中的每一项时,向外抛出onselect事宜
修正src/components/indexlist/indexlist.cml文件js部份:
handleScroll(e) {
let { scrollTop } = e.detail;
scrollTop = Math.ceil(scrollTop);
this.activeIndex = this.list.findIndex(item => scrollTop >= item.offsetTop && scrollTop < item.totalHeight + item.offsetTop )
},
handleSelect(e) {
this.$cmlEmit('onselect', e)
}
当前激活的索引(this.activeIndex)经由盘算获得,规则为:假如当前scroller转动的间隔在对应子列表地点的高度局限内,则以为该索引是激活的。
另一部份是索引地区,修正src/components/indexlist/indexlist.cml文件模板部份,增添索引地区模板内容:
<view
class="short-cut-wrapper"
style="{{compScwStyle}}"
>
<view
c-for="{{shortcut}}"
class="short-cut-item"
c-bind:tap="scrollToItem(item)"
>
<text class="short-cut-item-text" style="{{activeIndex === index ? 'color:orange' : ''}}">{{item}}</text>
</view>
</view>
在索引地区,我们要完成点击索引值主列表能够疾速定位到对应地区,修正src/components/indexlist/indexlist.cml文件js部份:
scrollToItem(shortcut) {
let { offsetTop } = this.list.find(item => item.name === shortcut);
this.offsetTop = offsetTop;
}
索引地区应当定位在视窗右边而且高低居中。因为chameleon暂时不支持在css中运用百分比,因而我们经由过程chameleon-api供应的对外接口猎取屏幕视窗高度,然后运用js盘算获得位置, 合营部份css来完成索引地区定位在视窗右边居中。修正src/components/indexlist/indexlist.cml文件js部份:
// computed
compScwStyle() {
return `top:${this.viewportHeight / 2}cpx`
}
// method
async getViewportHeight() {
let res = await cml.getSystemInfo();
this.viewportHeight = res.viewportHeight;
},
至此便经由过程chameleon语法一致完成了一个跨端indexlist组件,该组件直接能够在web、weex、微信小顺序、支付宝小顺序与百度小顺序五个端运转。为了轻易形貌,上述代码只是简朴引见了组件完成的中心代码,跳过了款式和一些逻辑细节。
组件运用
修正src/pages/index/index.cml文件内里的json设置,援用建立的indexlist组件
"base": {
"usingComponents": {
"indexlist": "/components/indexlist/indexlist"
}
},
修正src/pages/index/index.cml文件中的模板部份,援用建立的indexlist组件
<view class="page-wrapper">
<indexlist
dataList="{{dataList}}"
c-bind:onselect="onItemSelect"
/>
</view>
个中dataList是一个对象数组,示意组件要衬着的数据源
一些思索
本篇文章重要引见了怎样经由过程chameleon语法完成跨端组件。对照编写chameleon跨端组件的准确姿态(上篇).md)引见的经由过程第三方库封装的要领能够发明,两种体式格局是完整差别的,现细致对照一下这两种完成体式格局的上风与劣势, 并给出开辟发起:
上风 | 劣势 | 开辟发起 | |
基于第三方组件库完成 | – 可利用已有生态敏捷完成跨端组件 | – 组件的完成依靠第三方库,假如没有成熟的对应端第三方库则没法完成该端组件开辟 – 因为各端第三方组件存在差别,封装的跨端组件款式与功用存在差别 – 第三方组件晋级时,要对应调解跨端组件的完成,保护本钱较大 – 第三方组件库质量不能获得保证 | – 将基于各端第三方组件封装跨端组件库的要领作为暂时计划 – 关于迥殊庞杂而且已有成熟第三方库或许框架才能暂时不支持的组件,能够斟酌运用第三方组件封装成对应的跨端组件,比方图表组件、舆图组件等等 |
基于chameleon一致完成 | – 新的端接入时,能够直接运转 – 平常情况下,不存在各端款式与功用差别 – 绝大部份组件不需要各端差别化完成,运用chameleon语法完成开辟与保护本钱更低 – 能够导出原生组件供多端运用 | – 从零搭建时候与手艺本钱较高 | 从历久保护的角度来说,发起运用chameleon生态来一致完成跨端组件库 假如仅仅是各端api层面的差别,发起运用多态接口抹平差别,而不运用多态组件 |