在chameleon項目中咱們實現一個跨端組件通常有兩種思路:使用第三方組件封裝與基於chameleon語法統一實現。
在《編寫chameleon跨端組件的正確姿式(上篇)》中, 咱們介紹瞭如何使用第三方庫封裝跨端組件,可是絕大多數組件並不須要那樣差別化實現,絕大多數狀況下咱們推薦使用chameleon語法統一實現跨端組件。本篇是編寫chameleon跨端組件的正確姿式系列文章的下篇,與上篇給出的示例相同,本篇也以封裝一個跨端的indexlist組件爲例,首先介紹如何使用chameleon語法統一實現一個跨端組件,而後對比兩種組件開發方式並給出開發建議。javascript
如下效果依此爲weex端、web端、支付寶小程序端、微信小程序端以及百度小程序端: css
建立一個新項目 cml-demohtml
cml init project
進入項目java
cd cml-demo
cml init component
選擇「普通組件」, 並並輸入組件名字「indexlist」, 完成組件的建立, 建立以後的組件位於src/components/indexlist文件夾下。git
爲了方便說明,本例暫時實現一個具有基礎功能的indexlist組件。從功能方面講,indexlist組件主要由兩部分組成,主列表區域和索引區域。在用戶點擊組件右側索引時,主列表可以快速定位到對應區域;在用戶滑動組件主列表時,右側索引跟隨滑動不停切換當前索引項。從輸入輸出方面講,組件至少應該在用戶選擇某一項時拋出一個onselect事件,傳遞用戶當前所選中項的數據;至少應該接受一個datalist,做爲其渲染的數據源,這個datalist應該是一個相似於如下結構的對象數組:web
const dataList = [ { name: '阿里', pinYin: 'ali', }, { name: '北京', pinYin: 'beijing', }, ..... ]
根據設計的組件功能與輸入輸出, 咱們開始設計數據結構。
indexlist組件右側的索引列對應的數據結構爲一個數組,其中的每一項表示一個索引,具體結構以下:json
this.shortcut = [ 'A', 'B', 'C', ....]
indexlist組件的主列表區域對應的數據結構也是一個數組,其中的每一項表示一個子列表區域(例如以首字母a開頭的子列表)。下面咱們考慮每個子列表區域中至少應該包含的字段:小程序
由上面分析可得主列表區域數據結構以下:segmentfault
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表示滾動時觸發的事件。在主列表這一部分,咱們要實現以下功能:
修改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層面的不一樣,建議使用多態接口抹平差別,而不使用多態組件 |