「站在上帝的角度」谈谈Element组件结构-Radio

793 阅读5分钟

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

前言👋

  • 用户就是上帝,站在上帝的角度也就是站在使用者的角度去看待组件。
  • 用过不少优秀的UI库,用的时候美滋滋,轮到自己搭组件库的时候往往会去参考别人的源码。
  • 看完源码后恍然大悟 噢!原来可以这样写,但心里难免会有疑惑别人是怎么想出来这种解决思路的?🤳
  • 这一系列文章主要是面向未理解或者有疑惑的同学所以讲的比较基础,就让我们站在用户的角度去思考结构,看看换一种思路去写代码是不是有变化?

关于Radio组件💿

为什么我们会用到radio

作为用户👨‍💼

  • 在填写表单的时候,作为一个系统的使用者,我们下意识的习惯靠简单的鼠标点击或者手指触碰就可以选择你的答案而不是还要在键盘敲打文字。
  • 为什么?还不是为了方便,相信没人会愿意用十分钟来填写全是input输入框的调查问卷吧?这也是为什么基本上所有调查问卷都是单选框和多选框的原因。
  • 说白了就是便携方便点一下即可。

作为组件库使用者👨‍💻

  • 当我们将组件库的radio组件放到我们的页面我们想要的效果是什么?
    • 不花里胡哨
    • 可以满足基本的选中需求
    • 可以在基本的需求上进行定制增加功能(比如:禁用 大小 图标
  • 说白了就是我能写尽量少的代码来配置。

搭建组件⚒️

接下来可能用尽可能少的代码搭配element的源码进行结构说明,配合element Radio源码食用更加美味喔

基本架子🔨

  • 如果想要好看的radio或者说是自定义的radio组件那必不可能使用原生的radio直接放到页面上,所以要将整体架子进行改造。
  • 一个基本的radio架子主要有两个点,一个是原生的input用来接收被点击事件,另一个就是制造给用户看的一个圆圈和一个文字展示区,大概是这样子的。
<template>
  <label class="zl-radio">
    <span class="zl-radio__input">
      <span class="zl-radio__inner"></span>
      <input
        ref="radio"
        type="radio"
        class="zl-radio__original"
      >
    </span>
    <span>
      <slot></slot>
    </span>
  </label>
</template>
  • 可以看到在上面的代码中用label将所有包住是为了让用户不管选中按钮还是文字都可以选中,而label也巧妙地运用了隐式关联
  • 隐式的方式是把需要绑定的标签放到label内部,让label包裹上这个需要绑定的元素就相当于点击了radio
  • 上面那块span是表示前面的圆圈样式,inner类则是中间那个实心的样式,original类将inputopacity: 0;进行隐藏处理
  • 下面的span大家应该也很熟悉就是一个插槽来接受外部的文字

双向绑定🧽

  • 对于组件库使用者来说我们希望通过传入一个v-modellabel来控制我们表单的属性 image.png
  • 为了接收label,架子很简单只需要用props来接收即可,那v-model传入的值呢?我们都知道v-model其实也是语法糖,上面的代码也相当于
<l-radio label="第一个按钮" v-bind:value="radio" v-on:input="radio=$event"></l-radio>
<l-radio label="第二个按钮" v-bind:value="radio" v-on:input="radio=$event"></l-radio>
  • 那么我们需要在子组件中props增加一个value来接收传入的值,而在子组件中可以使用this.$emit('input', val)触发父组件上的input事件来改变选中
<script>
export default {
  props: {
    value: {},
    label:{},
  },
  computed:{
    model: {
      get() {
        return this.value;
      },
      set(val) {
        this.$emit('input', val);
      }
    },
  }
};
</script>
  • props接收传入的v-model的数值和label的数值,因为我们需要在子组件调用父组件的input事件,所以我们可以写一个计算属性分别用get接收父组件通过双向绑定传下来的value,通过set设置新的值,对应的template如下
<template>
  <label class="zl-radio">
    <span
      class="zl-radio__input"
      :class="{
        'is-checked': model === label
      }"
    >
      <span class="zl-radio__inner"></span>
      <input
        ref="radio"
        v-model="model"
        :value="label"
        type="radio"
        class="zl-radio__original"
      >
    </span>
    <span>
      <slot></slot>
      <template v-if="!$slots.default">{{ label }}</template>
    </span>
  </label>
</template>
  • 可以看到第一个span在样式上使用动态class如果使用者设置的label与传入的value相同时(即选中)则会有is-checked的选中样式
  • input标签里面也使用了v-model对上述的计算属性model来做一个双向绑定,这样的话我们子组件的input的值就跟外部的表单属性进行连接起来了也就实现了父组件与子组件的双向绑定
  • 因为有时候这个单选按钮的值就是那个文字的值所以在插槽下面加了一个template用于判断插槽中是否有文字时label的展现

由于本次只是分析结构,关于样式相关可以到这里查看传送门

更多属性需求🏁

  • 至此一个最简单Radio组件就完成了,实现了双向绑定
  • 但我们往往还需要一些定制比如说禁用按钮的实现,我们希望通过在组件上添加属性来实现

image.png

  • 对此我们要在子组件做的处理也是一样的props接收参数然后如果为true做些什么处理如果false做些什么处理
...
    <span
      class="zl-radio__input"
      :class="{
        'is-checked': model === label,
        'is-disabled': disabled
      }"
    >
      <span class="zl-radio__inner"></span>
      <input
        ref="radio"
        v-model="model"
        :value="label"
        type="radio"
        class="zl-radio__original"
        :disabled="disabled"
      >
    </span>
...
  • 就拿禁用来说我们拿到了属性就跟上面的is-checked一样做动态样式绑定禁用的样式再让子组件inputdisabled同步处理
  • 一种属性是这样其他属性也是一样的道理就像Element也定义了很多的动态类来做匹配包括边框尺寸等等

image.png

写在最后👋

  • 对于组件库的搭建我也在慢慢的摸索,讲的都是我自己得出来的分享所以说可能对于大佬来说会比较基础,但我相信我的不断输出可以帮助到一些有疑惑的同学。
  • 如果您觉得这篇文章有帮助到您的的话不妨🍉关注+点赞+收藏+评论+转发🍉支持一下哟~~😛

往期精彩🌅

如何优雅的使用Vuepress编写组件示例(上)👈

如何优雅的使用Vuepress编写组件示例(下)👈

样式命名有多难?浅谈BEM命名规范⚡

为了方便,我改了别人的轮子😅