Cube UI组件库在移动端项目的实践踩坑总结

设计师玉哲 移动 阅读 2,678
赞 14 收藏
二维码
手机扫码查看
反馈

一个移动端项目的UI框架选择

最近接手了一个移动端H5项目,需要做一套完整的用户端界面。本来想用Vant,但产品经理说要那种比较简洁的设计风格,Vant的组件太重了,看起来有点复杂。看了几个UI框架后最终选择了Cube UI,主要原因是它轻量而且样式比较清爽。

Cube UI组件库在移动端项目的实践踩坑总结

项目本身不算复杂,主要是商品列表、详情页、购物车这些常见模块,但对性能要求比较高,毕竟移动端的体验很重要。Cube UI在这个场景下用着还挺合适的,不过确实也遇到了不少坑。

基本配置和组件使用

安装过程很简单,npm install cube-ui -S 就搞定了。不过需要注意的是Cube UI有个配套的webpack插件,用来按需加载组件,这个配置差点把我整懵了。

先说安装依赖:

npm install babel-plugin-import -D
npm install webpack-bundle-analyzer -D

然后在.babelrc里配置:

{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2"
  ],
  "plugins": [
    "transform-vue-jsx",
    "transform-runtime",
    ["import", {
      "libraryName": "cube-ui",
      "libraryDirectory": "src/components"
    }]
  ]
}

这样配置后就可以按需引入了,比如只用按钮组件:

import { Button } from 'cube-ui'

export default {
  components: {
    'cube-button': Button
  }
}

实际使用中常用的几个组件:Button、Scroll、Picker、Input、Switch等。大部分组件都挺好用的,特别是Scroll组件,滚动体验比原生的好很多。

最大的坑:Scroll组件的性能问题

项目中最让我头疼的就是Scroll组件。开始用的时候觉得挺好的,滑动流畅,各种事件也齐全。但是数据量一上来就出问题了,商品列表超过50条数据就开始卡顿了。

一开始没在意,以为是数据渲染的问题。后来发现即使数据很少,只要DOM结构稍微复杂一点,Scroll就会变得很卡。查了很久才发现是better-scroll的内部机制问题,它会创建一个额外的wrapper来处理滚动,如果子元素过多,计算量就很大。

最开始我想通过虚拟滚动来解决,但Cube UI本身不支持虚拟滚动。折腾了半天发现一个临时方案:

<template>
  <cube-scroll 
    ref="scroll" 
    :data="visibleList" 
    :options="scrollOptions"
    @pulling-up="onPullingUp">
    <div class="item" v-for="item in visibleList" :key="item.id">
      <img :src="item.img" />
      <div class="info">
        <h3>{{ item.name }}</h3>
        <p class="price">¥{{ item.price }}</p>
      </div>
    </div>
  </cube-scroll>
</template>

<script>
export default {
  data() {
    return {
      list: [], // 所有数据
      visibleList: [], // 当前显示的数据
      pageSize: 20,
      currentPage: 1,
      scrollOptions: {
        pullUpLoad: {
          threshold: 0,
          txt: {
            more: '加载更多',
            noMore: '没有更多数据'
          }
        }
      }
    }
  },
  mounted() {
    this.loadPage(1)
  },
  methods: {
    loadPage(page) {
      // 模拟API请求
      setTimeout(() => {
        const start = (page - 1) * this.pageSize
        const end = start + this.pageSize
        const newData = this.list.slice(start, end)
        if (page === 1) {
          this.visibleList = newData
        } else {
          this.visibleList.push(...newData)
        }
        this.currentPage = page
        
        // 结束上拉加载状态
        if (this.$refs.scroll) {
          this.$refs.scroll.forceUpdate()
        }
        
        // 检查是否还有更多数据
        if (end >= this.list.length) {
          this.$refs.scroll.finishPullUp()
        }
      }, 500)
    },
    onPullingUp() {
      const totalPage = Math.ceil(this.list.length / this.pageSize)
      if (this.currentPage < totalPage) {
        this.loadPage(this.currentPage + 1)
      } else {
        this.$refs.scroll.finishPullUp()
      }
    }
  }
}
</script>

这个方案就是分页加载,每次只渲染一部分数据。虽然不是真正的虚拟滚动,但对于大多数场景来说已经够用了。唯一的问题是滚动位置会重置,用户体验不是特别好。

CSS样式的定制和覆盖

Cube UI的默认样式虽然不错,但产品总是想要一些定制化的修改。比如按钮的颜色、圆角大小、字体等等。刚开始我想直接修改源码,后来发现有更好的方法。

首先在main.js里定义全局样式变量:

import Vue from 'vue'
import Cube from 'cube-ui'

Vue.use(Cube, {
  theme: 'default'
})

然后可以通过CSS覆盖的方式来修改样式。这里有个技巧,给组件加上特殊的class来精确控制:

<template>
  <cube-button class="custom-btn" :action="submitForm">
    提交订单
  </cube-button>
</template>

<style scoped>
.custom-btn >>> .cube-btn {
  background-color: #ff6b35;
  border-radius: 24px;
  font-size: 16px;
}

.custom-btn >>> .cube-btn-active {
  background-color: #e55a2b;
}
</style>

>> 符号可以穿透scoped限制,这样就能精确控制组件内部的样式了。不过要注意,某些深层嵌套的样式可能还需要用深度选择器。

Picker组件的数据绑定问题

另一个踩坑的地方是Picker组件。官方文档里的例子比较简单,但实际项目中经常需要动态设置选中值。

比如地区选择,第一次进入页面需要根据已保存的数据来设置初始值。这里有个关键点,Picker的数据格式必须严格按照[{text: ‘选项1’, value: ‘value1’}]这样的格式,否则会出现奇怪的问题。

// 正确的数据格式
const areaData = [
  [
    { text: '北京', value: 'beijing' },
    { text: '上海', value: 'shanghai' },
    { text: '广州', value: 'guangzhou' }
  ],
  [
    { text: '东城区', value: 'dongcheng' },
    { text: '西城区', value: 'xicheng' },
    { text: '朝阳区', value: 'chaoyang' }
  ]
]

// 设置初始选中值
this.$refs.picker.setIndexByValue([0, 1]) // 第一个选择器选第0项,第二个选第1项

另外Picker有个bug,就是当数据更新时,之前的选择可能会丢失。我的解决方案是在watch里监听数据变化:

watch: {
  pickerData: {
    handler(newVal) {
      this.$nextTick(() => {
        // 数据更新后重新设置选中值
        if (this.lastSelectedIndex) {
          this.$refs.picker.setIndexByValue(this.lastSelectedIndex)
        }
      })
    },
    deep: true
  }
}

打包优化和体积控制

项目快上线的时候发现打包出来的文件有点大,主要就是Cube UI的各种组件都在。虽然用了按需加载,但还是有些组件被全量引入了。

通过webpack-bundle-analyzer分析后发现,better-scroll库占了很大的体积。查了一下发现Cube UI内部使用了better-scroll作为滚动库,这个库本身就挺大的。

最后的解决方案是配置babel-plugin-import的ignore属性,只引入真正使用的组件:

["import", {
  "libraryName": "cube-ui",
  "libraryDirectory": "src/components",
  "ignore": [
    "wheeler",
    "picker",
    "time-picker",
    "segment-picker",
    "selector",
    "cascade-picker",
    "image-preview"
  ]
}]

这样配置后包的体积减少了约30%,效果还是挺明显的。

总体评价和待优化点

用了一段时间Cube UI,总体来说还是比较满意的。组件质量不错,文档也比较完整,社区还算活跃。特别是在移动端的表现,确实比一些桌面端友好的UI框架要好。

主要优点:

  • 轻量级,适合移动端项目
  • 组件设计简洁,符合移动端交互习惯
  • Scroll组件的滚动体验很好
  • 文档相对完善

不足之处:

  • 虚拟滚动功能缺失
  • 某些组件的事件机制不够灵活
  • 国际化支持一般
  • 社区活跃度不如Vant等主流框架

目前项目还在持续优化中,后续计划把一些频繁使用的组件抽取出来,减少对Cube UI的依赖。虽然整体还行,但长远来看还是要考虑更稳定的方案。

经验总结

总的来说,Cube UI在中小型移动端项目中还是很适用的,特别是对性能要求不太苛刻的情况。对于大列表的场景,需要自己做一些优化处理,比如分页加载或者虚拟滚动的实现。

这次项目让我更深刻地理解了UI框架的局限性,没有哪个框架是完美的,关键是根据具体需求选择合适的技术方案。Cube UI在这个项目中表现基本合格,但也暴露出了一些潜在的问题,这些都是宝贵的经验。

以上是我踩坑后的总结,希望对你有帮助。有更优的实现方式欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论