在chameleon项目中咱们实现一个跨端组件通常有两种思路:使用第三方组件封装与基于chameleon语法统一实现。本篇是编写chameleon跨端组件的正确姿式系列文章的上篇,以封装一个跨端的indexlist组件为例,首先介绍如何优雅的使用第三方库封装跨端组件,而后给出编写chameleon跨端组件的建议。使用chameleon语法统一实现跨端组件请关注文章《编写chameleon跨端组件的正确姿式(下篇)》javascript
依靠强大的多态协议,chameleon项目中能够轻松使用各端的第三方组件封装本身的跨端组件库。基于第三方组件能够利用现有生态迅速实现需求,可是却存在不少缺点,例如多端第三方组件自己的功能与样式差别、组件质量得不到保证以及绝大部分组件并不须要经过多态组件差别化实现,这样反而提高了长期的维护成本;使用chameleon语法统一实现则能够完美解决上述问题,而且扩展一个新的端时现有组件能够直接运行。本文的最后也会详细对比一下两种方案的优劣。 css
所以,建议将经过第三方库实现跨端组件库做为临时方案,从长期维护的角度来说,建议开发者使用chameleon语法统一实现绝大部分跨端组件,只有一些特别复杂而且已有成熟第三方库或者框架能力暂时不支持的组件,才考虑使用第三方组件封装成对应的跨端组件。html
因为本文介绍的是使用第三方库封装跨端组件, 所以示例的indexlist组件采用第三方组件封装来实现, 经过chameleon统一实现跨端组件的方法能够看《编写chameleon跨端组件的正确姿式(下篇)》。vue
最终实现的indexlist效果图:java
使用各端第三方组件实现chameleon跨端组件须要以下前期准备:git
建立一个新项目 cml-demogithub
cml init project
进入项目web
cd cml-demo
开发一个模块时咱们首先应该根据功能肯定其输入与输出,对应到组件开发上来讲,就是要肯定组件的属性和事件,其中属性表示组件接受的输入,而事件则表示组件在特定时机对外的输出。 npm
为了方便说明,本例暂时实现一个具有基础功能的indexlist。一个indexlist组件至少应该在用户选择某一项时抛出一个onselect事件,传递用户当前所选中项的数据;至少应该接受一个datalist,做为其渲染的数据源,这个datalist应该是一个相似于如下结构的对象数组:json
const dataList = [ { name: '阿里', pinYin: 'ali', py: 'al' }, { name: '北京', pinYin: 'beijing', py: 'bj' }, ..... ]
因为本文介绍的是如何使用第三方库封装跨端组件,所以在肯定组件需求以及实现思路后去寻找符合要求的第三方库。在开发以前,做者调研了目前较为流行的各端组件库,推荐以下:
web端:
wx端:
weex端:
除了上述组件库以外,开发者也能够根据本身的实际需求去寻找通过包装以后符合预期的第三方库。截止文章编写时,做者未找到较成熟的支付宝及百度小程序第三方库,所以暂时先实现web、微信小程序以及weex端,这也体现出了使用第三方库扩展跨端组件的局限性:当没有成熟的对应端第三方库时,没法完成该端的组件开发;而使用chameleon语法统一实现则能够解决上述问题,扩展新的端时已有组件可以直接运行,无需额外扩展。 本文在实现indexlist组件时分别使用了cube-ui, iview weapp以及weex-ui, 如下会介绍具体的开发过程.
建立多态组件
cml init component
选择“多态组件”, 并输入组件名字“indexlist”, 完成组件的建立, 建立以后的组件位于src/components/indexlist文件夹下。
多态组件中的.interface文件利用接口校验语法对组件的属性和事件进行类型定义,保证各端的属性和事件一致。肯定了组件的属性与事件以后就开始编写.interface文件, 修改src/components/indexlist/indexlist.interface:
type eventDetail = { name: String, pinYin: String, py: String } type arrayItem = { name: String, pinYin: String, py: String } type arr = [arrayItem]; interface IndexlistInterface { dataList: arr, onselect(eventDetail: eventDetail): void }
具体的interface文件语法能够参考此处, 本文再也不赘述。
安装cube-ui
npm i cube-ui -S
在src/components/indexlist/indexlist.web.cml的json文件中引入cube-ui的indexlist组件
"base": { "usingComponents": { "cube-index-list": "cube-ui/src/components/index-list/index-list" } }
修改src/components/indexlist/indexlist.web.cml中的模板代码,引用cube-ui的indexlist组件:
<view class="index-list-wrapper"> <cube-index-list :data="list" @select="onItemSelect" /> </view>
修改src/components/indexlist/indexlist.web.cml中的js代码, 根据cube-ui文档将数据处理成符合其组件预期的结构, 并向上抛出onselect事件:
const words = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]; class Indexlist implements IndexlistInterface { props = { dataList: { type: Array, default() { return [] } } } data = { list: [], } methods = { initData() { const cityData = []; words.forEach((item, index) => { cityData[index] = {}; cityData[index].items = []; cityData[index].name = item; }); this.dataList.forEach((item) => { let firstName = item.pinYin.substring(0, 1).toUpperCase(); let index = words.indexOf(firstName); cityData[index].items.push(item) }); this.list = cityData; }, onItemSelect(item) { this.$cmlEmit('onselect', item); } } mounted() { this.initData(); } } export default new Indexlist();
编写必要的样式:
.index-list-wrapper { width: 750cpx; height: 1200cpx; }
以上便使用cube-ui完成了web端indexlist组件的开发,效果以下:
安装weex-ui
npm i weex-ui -S
在src/components/indexlist/indexlist.weex.cml的json文件中引入weex-ui的wxc-indexlist组件:
"base": { "usingComponents": { "wex-indexlist": "weex-ui/packages/wxc-indexlist" } }
修改src/components/indexlist/indexlist.weex.cml中的模板代码,引用weex-ui的wxc-indexlist组件:
<view class="index-list-wrapper"> <wex-indexlist :normal-list="list" @wxcIndexlistItemClicked="onItemSelect" /> </view>
修改src/components/indexlist/indexlist.weex.cml中的js代码:
class Indexlist implements IndexlistInterface { props = { dataList: { type: Array, default() { return [] } } } data = { list: [], } mounted() { this.initData(); } methods = { initData() { this.list = this.dataList; }, onItemSelect(e) { this.$cmlEmit('onselect', e.item); } } } export default new Indexlist();
编写必要样式,此时发现weex端与web端有部分重复样式,所以将样式抽离出来建立indexlist.less,在web端与weex端的cml文件中引入该样式
<style lang="less"> @import './indexlist.less'; </style>
indexlist.less文件内容:
.index-list-wrapper { width: 750cpx; height: 1200cpx; }
以上便使用weex-ui完成了weex端indexlist组件的开发,效果以下:
根据iview weapp文档, 首先到Github下载iview weapp代码,将dist目录拷贝到项目的src目录下,而后在src/components/indexlist/indexlist.wx.cml的json文件中引入iview的index与index-item组件:
"base": { "usingComponents": { "i-index":"/iview/index/index", "i-index-item": "/iview/index-item/index" } },
修改src/components/indexlist/indexlist.wx.cml中的模板代码,引用iview的index与index-item组件:
<view class="index-list-wrapper"> <i-index height="1200rpx" > <i-index-item wx:for="{{cities}}" wx:for-index="index" wx:key="{{index}}" wx:for-item="item" name="{{item.key}}" > <view class="index-list-item" wx:for="{{item.list}}" wx:for-index="in" wx:key="{{in}}" wx:for-item="it" c-bind:tap="onItemSelect(it)" > <text>{{it.name}}</text> </view> </i-index-item> </i-index> </view>
修改src/components/indexlist/indexlist.wx.cml中的js代码, 根据iview weapp文档将数据处理成符合其组件预期的结构, 并向上抛出onselect事件:
const words = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]; class Indexlist implements IndexlistInterface { props = { dataList: { type: Array, default() { return [] } } } data = { cities: [] } methods = { initData() { let storeCity = new Array(26); words.forEach((item,index)=>{ storeCity[index] = { key: item, list: [] }; }); this.dataList.forEach((item)=>{ let firstName = item.pinYin.substring(0,1).toUpperCase(); let index = words.indexOf(firstName); storeCity[index].list.push(item); }); this.cities = storeCity; }, onItemSelect(item) { this.$cmlEmit('onselect', item); } } mounted() { this.initData(); } } export default new Indexlist();
编写必要样式:
@import 'indexlist.less'; .index-list { &-item { height: 90cpx; padding-left: 20cpx; justify-content: center; border-bottom: 1cpx solid #F7F7F7 } }
以上便使用iview weapp完成了wx端indexlist组件的开发, 效果以下:
修改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是一个对象数组,表示组件要渲染的数据源。具体结构为:
const dataList = [ { name: '阿里', pinYin: 'ali', py: 'al' }, { name: '北京', pinYin: 'beijing', py: 'bj' }, ..... ]
根据上述例子能够看出,chameleon项目能够轻松结合第三方库封装本身的跨端组件库。使用第三方组件封装跨端组件库的步骤大体以下:
根据组件多态文档, 像indexlist.web.cml、indexlist.wx.cml与indexlist.weex.cml的这些文件是灰度区, 它们是惟一能够调用下层端能力的CML文件,这里的下层端能力既包含下层端组件,例如在web端和weex端的.vue文件等;也包含下层端的api,例如微信小程序的wx.pageScrollTo等。这一层的存在是为了调用下层端代码,各端具体的逻辑实现应该在下层来实现, 这种规范的好处是显而易见的: 随着业务复杂度的提高,各个下层端维护的功能逐渐变多,其中通用的部分又能够经过普通cml文件抽离出来被统一调用,这样能够保证差别化部分始终是最小集合,灰度区是存粹的;若是将业务逻辑都放在了灰度区,随着功能复杂度的上升,三端通用功能/组件就没法达到合理的抽象,致使灰度层既有相同功能,又有差别化部分,这显然不是开发者愿意看到的场景。
在灰度区的模板、逻辑、样式和json文件中分别具备以下规则:
模板
逻辑
样式
json文件
在各端对应的灰度区文件中都可以根据上述规范使用各端的原生语法,可是为了规范仍然建议使用chameleon体系的语法规则。整体来讲,灰度区能够认为是chameleon体系与各端原生组件/方法的衔接点,向下使用各端功能/组件,向上经过多态协议提供各端统一的调用接口。