自定义组合组件model和provide、inject的使用

209 阅读1分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

image.png

涉及技术点

  1. model
  2. provide、inject

在此模拟element-ui中checkboxGroup、checkbox的封装,并且支持选择对象的形式

使用方式

组件使用1: label支持传递item对象,但是需要给组件一个唯一标识的键名,这个是必须的 组件使用2、3: element组件的常规使用

<template>
  <div>
    {{ formData }}
    <acc-checkbox-group v-model="formData.groupValObj" label-key="id">
      <acc-checkbox v-for="item in checkboxList" :key="item.id" :label="item">
        {{ item.name }}
      </acc-checkbox>
    </acc-checkbox-group>
    <acc-checkbox-group v-model="formData.groupValId">
      <acc-checkbox
        v-for="item in checkboxList"
        :key="item.id"
        :label="item.id"
      >
        {{ item.name }}
      </acc-checkbox>
    </acc-checkbox-group>
    <acc-checkbox v-model="formData.checkbox" label="123"></acc-checkbox>
  </div>
</template>

<script>
export default {
  data () {
    return {
      formData: {
        groupValObj: [
          { "id": 1, "name": "aa" }
        ]
      },
      checkboxList: [
        { id: 1, name: 'aa' },
        { id: 2, name: 'bb' },
        { id: 3, name: 'cc' },
      ]
    }
  }
}
</script>

<style>
</style>

正题开始

AccCheckboxGroup组件

  1. model用来定义使用组件v-model绑定的参数字段。详情参考
  2. provideinject 用来向下传递数据和接收数据。详情参考
<template>
  <div>
    <slot />
  </div>
</template>

<script>
export default {
  name: 'AccCheckboxGroup',
  model: {
    prop: 'groupVal',
    event: 'changed'
  },
  provide () {
    return {
      checkboxGroup: this
    }
  },
  props: {
    // checkboxGroup组件 接受到groupVal值
    // 之后得触发一下 当前changed事件 动态改变父组件的groupVal值
    // checkboxGroup组件 操作 checkbox 组件 -> 使用provide 和 inject
    groupVal: {
      type: Array,
      default () {
        return []
      }
    },
    labelKey: {
      type: String,
      default: ''
    }
  },
  data () {
    return {}
  },
  methods: {}
}
</script>
<style lang="scss" scoped></style>

AccCheckbox组件

inject接收父组件的参数,default可以定义默认值。因为AccCheckbox组件可以单独使用。

<template>
  <label :class="['acc_checkbox', { 'is-checked': isChecked }]">
    <span class="acc_checkbox_input">
      <span class="acc_checkbox_inner"></span>
      <input
        type="checkbox"
        class="acc_checkbox_original"
        :name="name"
        v-model="model"
        :value="label"
      />
    </span>
    <span class="acc_checkbox_label">
      <slot />
      <!-- 如果 checkbox 没有传递内容时候 渲染label -->
      <template v-if="!$slots.default">{{ label }}</template>
    </span>
  </label>
</template>

<script>
export default {
  name: 'AccCheckbox',
  inject: {
    checkboxGroup: {
      default: ''
    }
  },
  props: {
    label: {
      type: [String, Number, Boolean, Object],
      default: ''
    },
    value: {
      type: Boolean,
      default: false
    },
    name: {
      type: String,
      default: ''
    }
  },
  data () {
    return {}
  },
  computed: {
    model: {
      get () {
        return this.isCheckGroup ? this.checkboxGroup.groupVal : this.value
      },
      set (value) {
        this.isCheckGroup ? this.checkboxGroup.$emit('changed', value) : this.$emit('input', value)
      }
    },
    isCheckGroup () {
      return !!this.checkboxGroup
    },
    isChecked () {
      return this.isCheckGroup
        ? this.checkboxGroup.labelKey
          ? this.model.findIndex(i => i[this.checkboxGroup.labelKey] === this.label[this.checkboxGroup.labelKey]) !== -1
          : this.model.includes(this.label)
        : this.model
    }
  },
  methods: {}
}
</script>
<style lang="scss" scoped>
.acc_checkbox {
  color: #606266;
  font-weight: 500;
  position: relative;
  cursor: pointer;
  display: inline-block;
  white-space: nowrap;
  outline: none;
  font-size: 14px;
  margin-right: 30px;
  .acc_checkbox_input {
    white-space: nowrap;
    cursor: pointer;
    outline: none;
    display: inline-block;
    line-height: 1;
    position: relative;
    vertical-align: middle;
    .acc_checkbox_inner {
      border: 1px solid #dcdfe6;
      width: 14px;
      height: 14px;
      background: #fff;
      position: relative;
      cursor: pointer;
      display: inline-block;
      box-sizing: border-box;
      &:after {
        width: 4px;
        height: 4px;
        border-radius: 100%;
        background: #fff;
        content: "";
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%) scale(0);
        transition: transform 0.15s ease-in;
      }
    }
    .acc_checkbox_original {
      opacity: 0;
      outline: none;
      position: absolute;
      z-index: -1;
      top: 0;
      left: 0;
      bottom: 0;
      right: 0;
      margin: 0;
    }
  }
  .acc_checkbox_label {
    font-size: 14px;
    padding-left: 10px;
  }
}
// checkbox 被选中
.acc_checkbox.is-checked {
  .acc_checkbox_input {
    .acc_checkbox_inner {
      border-color: $--color-primary;
      background: $--color-primary;
      &:after {
        transform: translate(-50%, -50%) scale(1);
      }
    }
  }
  .acc_checkbox_label {
    color: $--color-primary;
  }
}
</style>