在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體系與各端原生組件/方法的銜接點,向下使用各端功能/組件,向上經過多態協議提供各端統一的調用接口。