模仿element-ui封装vue组件库(四)

魏晓巍

发布于 2020.03.27 18:44 阅读 569 评论 0

七、封装一个element-ui风格的radio组件

前置知识点:

  1. radio的基本使用

参数支持:

参数名称 参数描述 参数类型 默认值
v-model 双向绑定 布尔类型 false
label 单选框和value值 string,num,Boolean ' '
name na'm    

7.1radio组件的基本框架和样式

框架、基本样式以及选中样式:

<template>
  <label class="one-radio is-checke">
    <span class="one-radio_input">
      <span class="one-radio_inner"></span>
      <input
      type="radio"
      class="one-radio_original"
      >
    </span>
    <span class="one-radio_label">我是label</span>
  </label>
</template>
<script>
export default {
  name: 'oneRadio',
  props: {}
  },
  watch: {},
  data () {
    return {}
  },
  methods: {}
}
</script>
<style lang="scss" scoped>
  .one-radio{
    color: #606266;
    font-weight: 500;
    line-height: 1;
    position: relative;
    cursor: pointer;
    display: inline-block;
    white-space: nowrap;
    outline: none;
    font-size: 14px;
    margin-right: 30px;
    -moz-user-select: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    .one-radio_input{
      white-space: nowrap;
      cursor: pointer;
      outline: none;
      display: inline-block;
      line-height: 1;
      position: relative;
      vertical-align: middle;
      .one-radio_inner{
        border: 1px solid #dcdfe6;
        border-radius: 100%;
        width: 14px;
        height: 14px;
        background-color: #fff;
        position: relative;
        cursor: pointer;
        display: inline-block;
        box-sizing: border-box;
        &:after{
          width: 4px;
          height: 4px;
          border-radius: 100%;
          background-color: #fff;
          content: "";
          position: absolute;
          left: 50%;
          top: 50%;
          transform: translate(-50%,-50%) scale(0);
          transition: transform .15s ease-in;
        }
      }
      .one-radio_original{
        opacity: 0;
        outline: none;
        position: absolute;
        z-index: -1;
        top: 0;
        left: 0px;
        right: 0;
        bottom: 0;
        margin: 0;
      }
    }
    .one-radio_label{
      font-size: 14px;
      padding-left: 10px;;
    }
  }
  // 选中的样式
  .one-radio.is-checked{
    .one-radio_input{
      .one-radio_inner{
        border-color: #409eff;
        background-color: #409eff;
        &:after{
          transform: translate(-50%,-50%) scale(1);
        }
      }
    }
    .one-radio_label{
      color:#409eff;
    }
  }
</style>

7.2radio组件的数据双向绑定

实现radio组件的数据双向绑定,除了要绑定数据本身外,还要控制radio组件的样式。

实现radio组件数据的绑定,需要父组件传递的label值和value值,其中value值使用v-model语法糖来绑定。

 <one-radio v-model="gender" label="0">男</one-radio>
 <one-radio v-model="gender" label="1">女</one-radio>

子组件接收数据后,要对数据进行处理。

当radio组件被点击时,绑定的数据应该变为该组件的label值。我们将组件中的input标签的value绑定为传入的label值,并且声明一个计算属性model双向绑定到input组件上,model我们需要通过get方法获取值;并且通过set方法将值回调给父组件。

同时,当我们在点击radio组件时,我们应该让被选中的组件添加选中样式,我们通过label和value的比较来判断,如果相同则显示选中样式。

<template>
  <label class="one-radio" :class="{'is-checked': label == value}">
    <span class="one-radio_input">
      <span class="one-radio_inner"></span>
      <input
      type="radio"
      class="one-radio_original"
      :value="label"
      v-model="model"
      >
    </span>
    <span class="one-radio_label">
      <slot></slot>
      <!-- 如果没有传值,就把label作为文本显示 -->
      <template v-if="!$slots.default">{{label}}</template>
      </span>
  </label>
</template>
//计算属性
computed: {
    model: {
      get () {
        return this.value
      },
      set (value) {
        // 触发父组件的input事件
        this.$emit('input', value)
      }
    }
  },

八、封装一个element-ui风格的radio-group组件

radio-group组件是再radio组件上进行优化的,它的目的是在我们使用radio组件时,不必给每个组件都添加一个v-model,而是通过绑定一个v-model来实现数据绑定。

使用radio-group组件包裹radio组件时,需要考虑到的一个问题就是radio-group组件于radio组件之间的通讯。我们在使用radio-group组件时将数据通过v-model进行了绑定,那么raido组件就不能直接拿到这个值,所以我们需要使用provide/inject进行祖孙组件之间得传值。

使用provide/inject非常简单,在radio-group中通过声明provide对象将组件自身进行传递,在radio中使用inject进行接收即可。

radio-group组件架构:

<template>
  <div class="one-radio-group">
    <slot></slot>
  </div>
</template>
<script>
export default {
  name: 'oneRadioGroup',
  provide () {
    return {
      RadioGroup: this
    }
  },
  props: {
    // 组件接收到了value值,我们需要触发这个组件的input事件
    // provide 与 inject  用来做祖孙之间得组件通讯
    value: null
  }
}
</script>

在radio组件中,通过inject可以直接接收到参数,此时,原本通过v-model传递进来的value值,变成了radio-group组件传进来的RadioGroup.value值,所以在computed计算属性中,我们先写一个radio组件是否被radio-group组件进行判断的方法,并且使用在model中,如果被包裹了,则使用RadioGroup.value值,否则使用value值。

同时在is-checked类的判断上抛弃label于value的比较,转而通过label于model(model此时的值为value或RadioGroup.value)比较,来进行样式的更改。

<template>
  <label class="one-radio" :class="{'is-checked': label == model}">
    <span class="one-radio_input">
      <span class="one-radio_inner"></span>
      <input
      type="radio"
      class="one-radio_original"
      :value="label"
      v-model="model"
      >
    </span>
    <span class="one-radio_label">
      <slot></slot>
      <!-- 如果没有传值,就把label作为文本显示 -->
      <template v-if="!$slots.default">{{label}}</template>
      </span>
  </label>
</template>
<script>
export default {
  name: 'oneRadio',
  props: {
    label: {
      type: [String, Number, Boolean],
      defualt: ''
    },
    value: null,
    name: {
      type: String,
      defualt: ''
    }
  },
  inject: {
    RadioGroup: {
      default: ''
    }
  },
  computed: {
    model: {
      get () {
        return this.isGroup ? this.RadioGroup.value : this.value
      },
      set (value) {
        // 触发父组件的input事件
        this.isGroup ? this.RadioGroup.$emit('input', value) : this.$emit('input', value)
      }
    },
    // 用于判断radio是否被radioGroup包裹
    isGroup () {
      return !!this.RadioGroup
    }
  }
}
</script>

--------------------------------------------至此,radio组件与radio-group组件封装完毕--------------------------------------------------

附radio组件代码:

<template>
  <label class="one-radio" :class="{'is-checked': label == model}">
    <span class="one-radio_input">
      <span class="one-radio_inner"></span>
      <input
      type="radio"
      class="one-radio_original"
      :value="label"
      v-model="model"
      >
    </span>
    <span class="one-radio_label">
      <slot></slot>
      <!-- 如果没有传值,就把label作为文本显示 -->
      <template v-if="!$slots.default">{{label}}</template>
      </span>
  </label>
</template>
<script>
export default {
  name: 'oneRadio',
  props: {
    label: {
      type: [String, Number, Boolean],
      defualt: ''
    },
    value: null,
    name: {
      type: String,
      defualt: ''
    }
  },
  inject: {
    RadioGroup: {
      default: ''
    }
  },
  computed: {
    model: {
      get () {
        return this.isGroup ? this.RadioGroup.value : this.value
      },
      set (value) {
        // 触发父组件的input事件
        this.isGroup ? this.RadioGroup.$emit('input', value) : this.$emit('input', value)
      }
    },
    // 用于判断radio是否被radioGroup包裹
    isGroup () {
      return !!this.RadioGroup
    }
  },
  data () {
    return {}
  },
  methods: {}
}
</script>
<style lang="scss" scoped>
  .one-radio{
    color: #606266;
    font-weight: 500;
    line-height: 1;
    position: relative;
    cursor: pointer;
    display: inline-block;
    white-space: nowrap;
    outline: none;
    font-size: 14px;
    margin-right: 30px;
    -moz-user-select: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    .one-radio_input{
      white-space: nowrap;
      cursor: pointer;
      outline: none;
      display: inline-block;
      line-height: 1;
      position: relative;
      vertical-align: middle;
      .one-radio_inner{
        border: 1px solid #dcdfe6;
        border-radius: 100%;
        width: 14px;
        height: 14px;
        background-color: #fff;
        position: relative;
        cursor: pointer;
        display: inline-block;
        box-sizing: border-box;
        &:after{
          width: 4px;
          height: 4px;
          border-radius: 100%;
          background-color: #fff;
          content: "";
          position: absolute;
          left: 50%;
          top: 50%;
          transform: translate(-50%,-50%) scale(0);
          transition: transform .15s ease-in;
        }
      }
      .one-radio_original{
        opacity: 0;
        outline: none;
        position: absolute;
        z-index: -1;
        top: 0;
        left: 0px;
        right: 0;
        bottom: 0;
        margin: 0;
      }
    }
    .one-radio_label{
      font-size: 14px;
      padding-left: 10px;;
    }
  }
  // 选中的样式
  .one-radio.is-checked{
    .one-radio_input{
      .one-radio_inner{
        border-color: #409eff;
        background-color: #409eff;
        &:after{
          transform: translate(-50%,-50%) scale(1);
        }
      }
    }
    .one-radio_label{
      color:#409eff;
    }
  }
</style>

附radio-group组件代码:

<template>
  <div class="one-radio-group">
    <slot></slot>
  </div>
</template>
<script>
export default {
  name: 'oneRadioGroup',
  provide () {
    return {
      RadioGroup: this
    }
  },
  props: {
    // 组件接收到了value值,我们需要触发这个组件的input事件
    // provide 与 inject  用来做祖孙之间得组件通讯
    value: null
  }
}
</script>

<style lang="scss" scoped>

</style>