UI组件库从1到N开发心得-组件篇

6,341 阅读4分钟

正文

距离第一篇UI组件库文章发布已经过去3个月了,在此期间利用零零散散的时间持续更新owl-ui组件库,目前owl-ui移动端组件库已经更新3大类(基础、表单、弹出层)9种组件(Button、Tabs、Input、Select、Switch、Drawer、Dialog、Picker、Toast)供使用。本篇文章主要讲述我在这3个月内开发UI组件的个人心得。如果想了解项目结构可以阅读上一篇文章,如果想了解实现原理可以阅读源码。所有连接在文章的结尾处。

我们从弹出层组件讲起

前方多图预警

先看效果图。



我在选择写组件的时候,首先选择做弹出层部分。为什么呢?我列出以下几点。

  1. 共性高、可复用。
  2. 兼容性高。
  3. 快速出货,提升成就感。

先说第一点:共性高、可复用

因为本人非常笨,所以做事之前需要构思很久,这样会减少之后重复性的工作。比如在做弹出层的时候,不少人会发现以上组件的共同点。没错,他们都是显示隐藏(废话)。我们接着往下分析,除了显示或隐藏之外,他们大部分都有遮罩层部分。还有动画效果也一致。我们先根据这几点把功能抽象出来,如何做呢?

在vue和less中都有mixins方式,根据mixins我们很方便的将组件中的共性提取出来,达到代码精简的目的。以下代码就是弹出层组件中显示隐藏功能的mixins文件。

// src/common/mixins/visibility.js

const EVENT_TOGGLE = 'toggle'

export default {
  model: {
    prop: 'visible',
    event: EVENT_TOGGLE
  },
  props: {
    visible: {
      type: Boolean,
      default: false
    },
    zIndex: {
      type: Number,
      default: 100
    },
    maskStyle: {
      type: Object,
      default: () => {}
    },
    containerStyle: {
      type: Object,
      default: () => {}
    }
  },
  data () {
    return {
      isVisible: false
    }
  },
  methods: {
    hide () {
      this.isVisible = false
    },
    show () {
      this.isVisible = true
    }
  },
  watch: {
    isVisible (val) {
      this.$emit('update:visible', val)
      this.$emit('callback', val)
      if (this.lockScroll) {
        document.body.style.overflow = val ? 'hidden' : ''
      }
    },
    visible: {
      handler (val) {
        this.isVisible = val
      }
    }
  },
  beforeDestroy () {
    this.lockScroll && (document.body.style.overflow = '')
  }
}

上面主要做一件事,根据visible属性判断组件展示隐藏

使用方式如下:

// 组件文件

<script>
import visibilityMixin from 'mixins/visibility'

export default {
    mixins: [ visibilityMixin ]
}
</script>

顺便说一下项目中运用到less的mixins运用,比如1px问题。

// src/styles/common/border.less

.min-device-pixel-ratio(@scale2, @scale3) {
  @media screen and (min-device-pixel-ratio: 2), (-webkit-min-device-pixel-ratio: 2) {
    transform: @scale2;
  }
  @media screen and (min-device-pixel-ratio: 3), (-webkit-min-device-pixel-ratio: 3) {
    transform: @scale3;
  }
}

.border-1px(@color: #DDD, @radius: 2PX, @style: solid) {
  &::before {
    content: "";
    pointer-events: none;
    display: block;
    position: absolute;
    left: 0;
    top: 0;
    transform-origin: 0 0;
    border: 1PX @style @color;
    border-radius: @radius;
    box-sizing: border-box;
    width: 100%;
    height: 100%;
    @media screen and (min-device-pixel-ratio: 2), (-webkit-min-device-pixel-ratio: 2) {
      width: 200%;
      height: 200%;
      border-radius: @radius * 2;
      transform: scale(.5);
    }
    @media screen and (min-device-pixel-ratio: 3), (-webkit-min-device-pixel-ratio: 3) {
      width: 300%;
      height: 300%;
      border-radius: @radius * 3;
      transform: scale(.33);
    }
  }
}

.border-top-1px(@color: #DDD, @style: solid) {
  &::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    border-top: 1Px @style @color;
    transform-origin: 0 0;
    .min-device-pixel-ratio(scaleY(.5), scaleY(.33));
  }
}

该函数可以通过传参改变线的颜色和类型。

使用方式如下:

// src/styles/packages/dialog.less

@import "../common/border";

@dialog-prefix-cls: ~"@{css-prefix}dialog";

.@{dialog-prefix-cls} {
    ...
    &-btns {
        ...
        .border-top-1px(); // Here
    }
}

合理的运用mixins可以使得项目结构清晰、减少冗余代码更利于后期维护。

优化方式有很多种,每个人有不同的编码习惯,因人而异。但是目标都是一致的,让自己的代码变得简洁、精炼和易读。

在弹出层中将公共部分抽象封装,比如遮罩层

// src/common/components/popup-mask.vue

<template>
  <div class="popup-mask"
       :style="{ ...maskStyle, zIndex: zIndex - 1 }"
       @click.stop.prevent="handleMask"></div>
</template>

<script>
export default {
  props: {
    name: String,
    maskStyle: Object,
    zIndex: Number
  },
  methods: {
    handleMask (event) {
      this.$emit('click', event)
    }
  }
}
</script>

第二点:兼容性高

我在工作中接触的移动端需求比较多,PC端做过一些管理后台。移动端与PC端的项目,给我最最直观的感受是,移动端要求UI极其严格,一像素都不能差,而PC端差不多就可以了,UI设计师们也不会过多纠结PC端做出来的页面是否跟原型图完全吻合。

在移动端,有的产品特别喜欢更改UI设计,特别是有表单部分的页面,今天产品嫌弃字体小了,明天可能觉得字体又太大了;今天把输入框改成圆角,明天就喜欢直角。今天觉得横向布局好,没准明天就要试试纵向布局。产品觉得这么改不要紧,殊不知如果项目中运用了UI组件库,这么修改完后,代码冗余太多。都是为了更好的用户体验,慢慢也能理解。在那之后的几个移动端项目里,表单部分基本不会用到UI组件库。但是弹出层部分没有过多的限制,据我了解到的产品内部最统一的就是弹出层。如果有同学想用owl-ui的弹出层部分的话,可以放心用,支持按需加载。

第三点:快速出货,提升成就感

我喜欢把长期计划拆解成多个很小的事情来做,就是制定很多小目标。好比游戏进度条一样,使其量化。之所以这么做的原因是,我能周期性的看到我的工作成果,这样可以激励自己,提升信心。

弹出层组件中,mixins做好之后,像toast、dialog、drawer组件只剩下设计api部分而已。而picker组件是基于drawer组件来实现的内容部分而已。当picker组件实现完成,这时已经说明表单的select组件也已经完成了。说起来简单,其实做起来也不难。

最后在使用弹出层组件时,我想用api调用的方式来使用它。这里我借鉴了cube-ui的vue-create-api,但是因为部分方法不太适合我,所以我稍加改动,借鉴(抄)到自己的库中。

比如Toast组件,官网给出的使用方式如下:

const toast = this.$createToast({
    time: 1000,
    txt: 'Toast time 1s'
})
toast.show()

我是个懒人,对我来说使用一个消息提醒要写这么多,我就觉得很烦恼,所以我在owl-ui中把vue-create-api稍加改造后,Toast使用方式如下:

this.$toast('欢迎光临')

清爽了很多。

其他类型组件使用情况,有人喜欢整套使用,有人喜欢部分使用。而我属于后者。

结语

我写组件库的目的就两点。第一点可以帮助我重新梳理一遍vue的知识体系,了解到自身的不足,不断的克服困难,让自己成长。第二点结识更多圈内的朋友,提高见识。我会持续更新迭代owl-ui组件库,欢迎喜欢技术的朋友多提建议。最后附上吴军博士说过话,这句话让我终身受益。

什么事情从0分做到50分靠常识,从50分做到90分靠技术,从90分做到100分靠的是艺术。做到90分我们可以通过努力能达到,至于是否能做到更好,就依人而定了。

链接

UI组件库从0到1开发心得

github

owl-ui主页

vue-create-api更改版

less版1px方案

祝工作顺利

邓文斌

2019年5月20日