资讯专栏INFORMATION COLUMN

编写chameleon跨端组件的正确姿势(上篇)

h9911 / 3170人阅读

摘要:使用语法统一实现跨端组件请关注文章编写跨端组件的正确姿势下篇依靠强大的多态协议,项目中可以轻松使用各端的第三方组件封装自己的跨端组件库。这种做法同时解决了组件命名冲突问题,例如在微信小程序端引用表示调用小程序原生的组件而不是内置的组件。

在chameleon项目中我们实现一个跨端组件一般有两种思路:使用第三方组件封装与基于chameleon语法统一实现。本篇是编写chameleon跨端组件的正确姿势系列文章的上篇,以封装一个跨端的indexlist组件为例,首先介绍如何优雅的使用第三方库封装跨端组件,然后给出编写chameleon跨端组件的建议。使用chameleon语法统一实现跨端组件请关注文章《编写chameleon跨端组件的正确姿势(下篇)》

依靠强大的多态协议,chameleon项目中可以轻松使用各端的第三方组件封装自己的跨端组件库。基于第三方组件可以利用现有生态迅速实现需求,但是却存在很多缺点,例如多端第三方组件本身的功能与样式差异、组件质量得不到保证以及绝大部分组件并不需要通过多态组件差异化实现,这样反而提升了长期的维护成本;使用chameleon语法统一实现则可以完美解决上述问题,并且扩展一个新的端时现有组件可以直接运行。本文的最后也会详细对比一下两种方案的优劣。

因此,建议将通过第三方库实现跨端组件库作为临时方案,从长期维护的角度来讲,建议开发者使用chameleon语法统一实现绝大部分跨端组件,只有一些特别复杂并且已有成熟第三方库或者框架能力暂时不支持的组件,才考虑使用第三方组件封装成对应的跨端组件。

由于本文介绍的是使用第三方库封装跨端组件, 因此示例的indexlist组件采用第三方组件封装来实现, 通过chameleon统一实现跨端组件的方法可以看《编写chameleon跨端组件的正确姿势(下篇)》。

最终实现的indexlist效果图:

前期准备

使用各端第三方组件实现chameleon跨端组件需要如下前期准备:

项目初始化

创建一个新项目 cml-demo

</>复制代码

  1. cml init project

进入项目

</>复制代码

  1. cd cml-demo
组件设计

开发一个模块时我们首先应该根据功能确定其输入与输出,对应到组件开发上来说,就是要确定组件的属性和事件,其中属性表示组件接受的输入,而事件则表示组件在特定时机对外的输出。

为了方便说明,本例暂时实现一个具备基础功能的indexlist。一个indexlist组件至少应该在用户选择某一项时抛出一个onselect事件,传递用户当前所选中项的数据;至少应该接受一个datalist,作为其渲染的数据源,这个datalist应该是一个类似于以下结构的对象数组:

</>复制代码

  1. const dataList = [
  2. {
  3. name: "阿里",
  4. pinYin: "ali",
  5. py: "al"
  6. }, {
  7. name: "北京",
  8. pinYin: "beijing",
  9. py: "bj"
  10. },
  11. .....
  12. ]
寻找第三方组件库

由于本文介绍的是如何使用第三方库封装跨端组件,因此在确定组件需求以及实现思路后去寻找符合要求的第三方库。在开发之前,作者调研了目前较为流行的各端组件库,推荐如下:

web端:

cube-ui

vux

mint-ui

vant

wx端:

iview weapp

vant weapp

weui

weex端:

weex-ui

除了上述组件库之外,开发者也可以根据自己的实际需求去寻找经过包装之后符合预期的第三方库。截止文章编写时,作者未找到较成熟的支付宝及百度小程序第三方库,因此暂时先实现web、微信小程序以及weex端,这也体现出了使用第三方库扩展跨端组件的局限性:当没有成熟的对应端第三方库时,无法完成该端的组件开发;而使用chameleon语法统一实现则可以解决上述问题,扩展新的端时已有组件能够直接运行,无需额外扩展。 本文在实现indexlist组件时分别使用了cube-ui, iview weapp以及weex-ui, 以下会介绍具体的开发过程.

组件开发
初始化

创建多态组件

</>复制代码

  1. cml init component

选择“多态组件”, 并输入组件名字“indexlist”, 完成组件的创建, 创建之后的组件位于src/components/indexlist文件夹下。

接口校验

多态组件中的.interface文件利用接口校验语法对组件的属性和事件进行类型定义,保证各端的属性和事件一致。确定了组件的属性与事件之后就开始编写.interface文件, 修改src/components/indexlist/indexlist.interface:

</>复制代码

  1. type eventDetail = {
  2. name: String,
  3. pinYin: String,
  4. py: String
  5. }
  6. type arrayItem = {
  7. name: String,
  8. pinYin: String,
  9. py: String
  10. }
  11. type arr = [arrayItem];
  12. interface IndexlistInterface {
  13. dataList: arr,
  14. onselect(eventDetail: eventDetail): void
  15. }

具体的interface文件语法可以参考此处, 本文不再赘述。

web端组件开发

安装cube-ui

</>复制代码

  1. npm i cube-ui -S

在src/components/indexlist/indexlist.web.cml的json文件中引入cube-ui的indexlist组件

</>复制代码

  1. "base": {
  2. "usingComponents": {
  3. "cube-index-list": "cube-ui/src/components/index-list/index-list"
  4. }
  5. }

修改src/components/indexlist/indexlist.web.cml中的模板代码,引用cube-ui的indexlist组件:

</>复制代码

修改src/components/indexlist/indexlist.web.cml中的js代码, 根据cube-ui文档将数据处理成符合其组件预期的结构, 并向上抛出onselect事件:

</>复制代码

  1. 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"];
  2. class Indexlist implements IndexlistInterface {
  3. props = {
  4. dataList: {
  5. type: Array,
  6. default() {
  7. return []
  8. }
  9. }
  10. }
  11. data = {
  12. list: [],
  13. }
  14. methods = {
  15. initData() {
  16. const cityData = [];
  17. words.forEach((item, index) => {
  18. cityData[index] = {};
  19. cityData[index].items = [];
  20. cityData[index].name = item;
  21. });
  22. this.dataList.forEach((item) => {
  23. let firstName = item.pinYin.substring(0, 1).toUpperCase();
  24. let index = words.indexOf(firstName);
  25. cityData[index].items.push(item)
  26. });
  27. this.list = cityData;
  28. },
  29. onItemSelect(item) {
  30. this.$cmlEmit("onselect", item);
  31. }
  32. }
  33. mounted() {
  34. this.initData();
  35. }
  36. }
  37. export default new Indexlist();

编写必要的样式:

</>复制代码

  1. .index-list-wrapper {
  2. width: 750cpx;
  3. height: 1200cpx;
  4. }

以上便使用cube-ui完成了web端indexlist组件的开发,效果如下:

weex端组件开发

安装weex-ui

</>复制代码

  1. npm i weex-ui -S

在src/components/indexlist/indexlist.weex.cml的json文件中引入weex-ui的wxc-indexlist组件:

</>复制代码

  1. "base": {
  2. "usingComponents": {
  3. "wex-indexlist": "weex-ui/packages/wxc-indexlist"
  4. }
  5. }

修改src/components/indexlist/indexlist.weex.cml中的模板代码,引用weex-ui的wxc-indexlist组件:

</>复制代码

修改src/components/indexlist/indexlist.weex.cml中的js代码:

</>复制代码

  1. class Indexlist implements IndexlistInterface {
  2. props = {
  3. dataList: {
  4. type: Array,
  5. default() {
  6. return []
  7. }
  8. }
  9. }
  10. data = {
  11. list: [],
  12. }
  13. mounted() {
  14. this.initData();
  15. }
  16. methods = {
  17. initData() {
  18. this.list = this.dataList;
  19. },
  20. onItemSelect(e) {
  21. this.$cmlEmit("onselect", e.item);
  22. }
  23. }
  24. }
  25. export default new Indexlist();

编写必要样式,此时发现weex端与web端有部分重复样式,因此将样式抽离出来创建indexlist.less,在web端与weex端的cml文件中引入该样式

</>复制代码

indexlist.less文件内容:

</>复制代码

  1. .index-list-wrapper {
  2. width: 750cpx;
  3. height: 1200cpx;
  4. }

以上便使用weex-ui完成了weex端indexlist组件的开发,效果如下:

wx端组件编写

根据iview weapp文档, 首先到Github下载iview weapp代码,将dist目录拷贝到项目的src目录下,然后在src/components/indexlist/indexlist.wx.cml的json文件中引入iview的index与index-item组件:

</>复制代码

  1. "base": {
  2. "usingComponents": {
  3. "i-index":"/iview/index/index",
  4. "i-index-item": "/iview/index-item/index"
  5. }
  6. },

修改src/components/indexlist/indexlist.wx.cml中的模板代码,引用iview的index与index-item组件:

</>复制代码

  1. {{it.name}}

修改src/components/indexlist/indexlist.wx.cml中的js代码, 根据iview weapp文档将数据处理成符合其组件预期的结构, 并向上抛出onselect事件:

</>复制代码

  1. 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"];
  2. class Indexlist implements IndexlistInterface {
  3. props = {
  4. dataList: {
  5. type: Array,
  6. default() {
  7. return []
  8. }
  9. }
  10. }
  11. data = {
  12. cities: []
  13. }
  14. methods = {
  15. initData() {
  16. let storeCity = new Array(26);
  17. words.forEach((item,index)=>{
  18. storeCity[index] = {
  19. key: item,
  20. list: []
  21. };
  22. });
  23. this.dataList.forEach((item)=>{
  24. let firstName = item.pinYin.substring(0,1).toUpperCase();
  25. let index = words.indexOf(firstName);
  26. storeCity[index].list.push(item);
  27. });
  28. this.cities = storeCity;
  29. },
  30. onItemSelect(item) {
  31. this.$cmlEmit("onselect", item);
  32. }
  33. }
  34. mounted() {
  35. this.initData();
  36. }
  37. }
  38. export default new Indexlist();

编写必要样式:

</>复制代码

  1. @import "indexlist.less";
  2. .index-list {
  3. &-item {
  4. height: 90cpx;
  5. padding-left: 20cpx;
  6. justify-content: center;
  7. border-bottom: 1cpx solid #F7F7F7
  8. }
  9. }

以上便使用iview weapp完成了wx端indexlist组件的开发, 效果如下:

组件使用

修改src/pages/index/index.cml文件里面的json配置,引用创建的indexlist组件

</>复制代码

  1. "base": {
  2. "usingComponents": {
  3. "indexlist": "/components/indexlist/indexlist"
  4. }
  5. },

修改src/pages/index/index.cml文件中的模板部分,引用创建的indexlist组件

</>复制代码

其中dataList是一个对象数组,表示组件要渲染的数据源。具体结构为:

</>复制代码

  1. const dataList = [
  2. {
  3. name: "阿里",
  4. pinYin: "ali",
  5. py: "al"
  6. }, {
  7. name: "北京",
  8. pinYin: "beijing",
  9. py: "bj"
  10. },
  11. .....
  12. ]
开发总结

根据上述例子可以看出,chameleon项目可以轻松结合第三方库封装自己的跨端组件库。使用第三方组件封装跨端组件库的步骤大致如下:

跨端组件设计

根据实际需求引入合适的第三方组件

根据第三方组件文档,将数据处理成符合预期的结构,并在适当时机抛出事件

编写必要样式

一些思考
理解*.[web|wx|weex].cml

根据组件多态文档, 像indexlist.web.cml、indexlist.wx.cml与indexlist.weex.cml的这些文件是灰度区, 它们是唯一可以调用下层端能力的CML文件,这里的下层端能力既包含下层端组件,例如在web端和weex端的.vue文件等;也包含下层端的api,例如微信小程序的wx.pageScrollTo等。这一层的存在是为了调用下层端代码,各端具体的逻辑实现应该在下层来实现, 这种规范的好处是显而易见的: 随着业务复杂度的提升,各个下层端维护的功能逐渐变多,其中通用的部分又可以通过普通cml文件抽离出来被统一调用,这样可以保证差异化部分始终是最小集合,灰度区是存粹的;如果将业务逻辑都放在了灰度区,随着功能复杂度的上升,三端通用功能/组件就无法达到合理的抽象,导致灰度层既有相同功能,又有差异化部分,这显然不是开发者愿意看到的场景。
在灰度区的模板、逻辑、样式和json文件中分别具有如下规则:

模板

调用下层组件时,既可以使用chameleon语法,也可以使用各端原生语法;在灰度区chameleon编译器不会编译各个端原生语法,例如v-for,bindtap等。建议在模板部分仍然使用chameleon模板语法,只有在实现对应平台不支持的语法(例如web端v-html等)时才使用原生语法。

引用下层全局组件时需要添加origin-前缀,这样可以“告诉”chameleon编译器是在引用下层的原生组件,chameleon编译器就不会对其进行处理了。这种做法同时解决了组件命名冲突问题,例如在微信小程序端引用表示调用小程序原生的button组件而不是chameleon内置的button组件。

逻辑

在script逻辑代码中,除了编写普通cml逻辑代码之外,开发者还可以使用下层端的全局变量和任意方法,包括生命周期函数。这种机制保证开发者可以灵活扩展各端特有功能,而不需要依赖多态接口。

样式

既可以使用cmss语法也可以使用下层端的css语法。

json文件

*web.cml:base.usingComponents可以引入普通cml组件和任意.vue扩展名组件,路径规则见组件配置。

*wx.cml:base.usingComponents可以引入普通cml组件和普通微信小程序组件,路径规则见组件配置。

*weex.cml:base.usingComponents可以引入普通cml组件和任意.vue扩展名组件,路径规则见组件配置。

在各端对应的灰度区文件中均可以根据上述规范使用各端的原生语法,但是为了规范仍然建议使用chameleon体系的语法规则。总体来说,灰度区可以认为是chameleon体系与各端原生组件/方法的衔接点,向下使用各端功能/组件,向上通过多态协议提供各端统一的调用接口。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/102465.html

相关文章

  • 编写chameleon跨端组件正确姿势(下篇)

    摘要:在编写跨端组件的正确姿势上篇中,我们介绍了如何使用第三方库封装跨端组件,但是绝大多数组件并不需要那样差异化实现,绝大多数情况下我们推荐使用语法统一实现跨端组件。 在chameleon项目中我们实现一个跨端组件一般有两种思路:使用第三方组件封装与基于chameleon语法统一实现。在《编写chameleon跨端组件的正确姿势(上篇)》中, 我们介绍了如何使用第三方库封装跨端组件,但是绝大...

    xiongzenghui 评论0 收藏0
  • 【重磅】Chameleon 开放跨端扩展标准协议

    摘要:基于对跨端工作的积累,规范了一套跨端标准,称之为协议开发者只需要按照标准扩展流程,即可快速扩展任意架构模式的终端。实现了微信端的基本扩展,用户可以以此为模板进行开发。新框架太多?学不动啦?有这一套跨端标准,今后再也不用学习新框架了。各个小程序按自己喜好各自为政?有了这套标准,再也不用重复开发各种新平台啦。如今前端比较流行的 React Native、Weex、Flutter 等跨平台开发框架...

    liangdas 评论0 收藏0
  • 【重磅】Chameleon 开放跨端扩展标准协议

    摘要:但是从年微信推出小程序,到至今各大厂商都推出自己的小程序,跨端开发就不仅仅是技术的问题了。实现了微信端的基本扩展,用户可以以此为模板进行开发。 新框架太多?学不动啦?有这一套跨端标准,今后再也不用学习新框架了。 各个小程序按自己喜好各自为政?有了这套标准,再也不用重复开发各种新平台啦。 如今前端比较流行的 React Native、Weex、Flutter 等跨平台开发框架,对于开发来...

    shuibo 评论0 收藏0
  • 开源中国专访:Chameleon原理首发,其它跨多端统一框架都是假

    摘要:中国互联网络信息中心发布的中国互联网络发展状况统计报告显示,截至年月,我国网民规模达亿人,微信月活亿支付宝月活亿百度月活亿另一方面,中国手机占智能手机整体的比例超过,月活约亿。在年末正式发布了面向未来的跨端的。 开源中国专访:Chameleon原理首发,其它跨多端统一框架都是假的? 原创: 嘉宾-张楠 开源中国 以往我们说某一功能跨多端,往往是指在诸如 PC、移动等不同类型的设备之...

    GraphQuery 评论0 收藏0

发表评论

0条评论

h9911

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<