Element-ui checkbox

4,419 阅读2分钟

Element-ui checkbox

image.png

<template>
  <!-- `checked`truefalse -->
  <el-checkbox v-model="checked">备选项</el-checkbox>
</template>
<script>
  export default {
    data() {
      return {
        checked: true
      };
    }
  };
</script>

剖析

  • checkbox 有三种状态:选中,取消选中,半选
  • checkbox 有三种使用情况:
  • 单独使用, v-model绑定的值为布尔类型
  • 单独使用,v-model绑定的值为数组类型,且需与label搭配
  • 与checkbox-group搭配使用,且需与label搭配(checkbox-group见下回)

测试代码

<template>
  <div>
    <p>checkbox</p>
    <!-- 第一种情况 -->
    <y-checkbox v-model="checkboxValue" @change="handleChange">杭州</y-checkbox>
    <!-- 第二种情况 -->
    <y-checkbox v-model="checkboxArr" label="1" @change="handleChange">杭州</y-checkbox>
    <!-- 半选中状态 -->
    <y-checkbox 
      :indeterminate="checkboxIndeterminate" 
      v-model="checkboxIndeterminateValue"
      @change="handleIndeterminate">半选中状态</y-checkbox>
  </div>
</template>

<script>
export default {
  data() {
    return {
      checkboxValue: true,
      checkboxArr: [
        '1',
        '2',
        '3',
      ],
      checkboxIndeterminate: true,
      checkboxIndeterminateValue: false,
    }
  },
  watch: {
    checkboxValue() {
      console.log('父组件checkboxValue', this.checkboxValue)
    }
  },
  methods: {
    handleChange(data) {
      console.log('父组件change事件', data);
    },
    handleIndeterminate(data) {
      console.log('半选', data);
      // 将半选控制标识更改为false, 便可以正常选中和取消选中了
      this.checkboxIndeterminate = false;
    }
  }
}
</script>
  • 注意:单独使用checkbox时,且设置了半选中:indeterminate="checkboxIndeterminate" ,则需要绑定change事件以更改该checkboxIndeterminate为false,从而正常选中和取消选中

组件checkbox

template
<template>
    <!-- checkbox选中状态: 选中, 不选中, 半选
        单独使用checkbox,如何判断选中状态呢?
        prop接收的value值类型需为布尔类型: true选中, false不选中
        半选中状态如何处理?
        半选中状态下,如果再次点击,将indeterminate置为false, 便可以选中和取消选中了
        但是只能在父组件中控制indeterminate的值(因为子组件不能更改父组件prop传进来的值) -->
    <label
        class="y-checkbox"
        role="checkbox">
        <!-- checkbox分两部分, 一部分是多选框,一部分是标签 -->
        <span 
            class="y-checkbox__input"
            :class="{
                'is-checked': isChecked,
                'is-indeterminate': indeterminate,
            }"
            >
            <!-- y-checkbox__inner 替换了原生多选框的样式,原生多选框只有两种状态,通过自定义的样式可定义第三种半选状态-->
            <span class="y-checkbox__inner"></span>
            <!-- 原生多选框, 如果value和labl都没有传, 则选中状态变化后, model值为true或false 
                定义ref以便获取该dom元素从而通知该原生多选框的选中状态
                自定义属性 aria-hidden 可直观明白该html是否展示
            -->
            <input
                ref="checkbox"
                class="y-checkbox__original"
                type="checkbox"
                :value="label"
                aria-hidden="false"
                v-model="model"
                @change="handleChange"/>    
        </span>
        <!-- 标签, 如果子元素存在则展示子元素, 否则标签为label -->
        <span 
            class="y-checkbox__label">
            <slot></slot>
            <templat v-if="!$slots.default">{{label}}</templat>
        </span>
    </label>
</template>

scripts

checkbox 选中状态判定

<script>
export default {
    name: 'YCheckbox',
    props: {
        value: {},
        // label选中状态的值, 只有在checkbox-group或者value为数组类型的时候方可有效
        label: {},
        // 半选中状态
        indeterminate: Boolean,
    },
    computed: {
        // 原生多选框值的选中状态
        model: {
            get() {
                return this.value;
            },
            set(val) {
                console.log('model发生了变化', val);
                /**
                 * 通知父组件value值发生了变化
                 * v-model双向绑定, 手动通知父组件吗?这是因为并非是value值发生了变化,
                 * 而是另一个依赖变量model发生了变化, model发生变化后value也要变, 所以需要手动触发
                 */
                this.$emit('input', val);
            }
        },
        /**
         * 选中状态的判定
         * 如何判定选中状态?
         * 这里分三种情况,一种是单个使用(value为布尔类型, label为undefined)
         * 一种情况是单个使用(value为数组类型, label为数组中的某一项)
         * 一种情况是父组件为checkbox-group(value为undefined, label有值)
         */
        isChecked() {
            // this.model.toString(), {}.toString.call(this.model)有什么区别?
            // 若this.model=true, 则 this.model.toString()->true, {}.toString.call(this.model)->[object Boolean]
            console.log('第一种情况', {}.toString.call(this.model), {}.toString.call(this.model) === '[object Boolean]')
            
            // 如果是第一种情况, 单独使用多选框且value值为布尔类型
            if({}.toString.call(this.model) === '[object Boolean]') {
                return this.model;
            }
            // 如果是第二种情况,单独使用且value为数组类型
            // 数组的判定类型有几种?
            // console.log('第二种情况', this.model, Array.isArray(this.model), this.model.indexOf(this.label), this.model.includes(this.label));
            else if(Array.isArray(this.model)) {
                console.log(this.model, this.label);
                // 判断值是否存在于数组中
                // indexOf和includes的区别
                return this.model.indexOf(this.label) > -1;
            }
            // 第三种情况,父组件是checkbox-group
            console.log('第三种情况,父组件是checkbox-group');
        }
    },
    methods: {
        /**
         * 父组件可能有change事件,即选中状态变化后的回调
         * 因为这是由原生多选框选中状态变化后的回调,需要等视图更新后方可获取到新值,并将新值传给回调函数
         */
        handleChange() {
            // console.log('原生checkbox发生了变化', this.model);
            this.$nextTick(()=>{
                this.$emit('change', this.model);
            })
        },
    },
    mounted() {
        console.log('checkbox', this.value, this.label);
        // console.log(this.model.toString(), {}.toString.call(this.model), {}.toString.call(this.model) === '[object Boolean]')
    },
    watch: {
        isChecked() {
            console.log('watchisChecked', this.isChecked)
        }
    }
}
</script>
  • this.model.toString(){}.toString.call(this.model)的区别
  • indexOfincludes 的区别

style

.y-checkbox {
    position: relative;
    margin-right: 30px;
}
.y-checkbox:last-child {
    margin-right: 0;
}
.y-checkbox__input {
    position: relative;
}
.y-checkbox__inner {
    display: inline-block;
    position: relative;
    width: 14px;
    height: 14px;
    border: 1px solid #dcdfe6;
    border-radius: 2px;
    box-sizing: border-box;
    vertical-align: middle;
}
.y-checkbox__inner::after {
    content: '';
    display: content-box;
    width: 3px;
    height: 7px;
    position: absolute;
    top: 2px;
    left: 4px;
    box-sizing: border-box;
    border: 1px solid #fff;
    border-top: 0;
    border-left: 0;
    transform: rotate(0deg) scale(0);
}
/* 选中样式 */
.y-checkbox__input.is-checked .y-checkbox__inner {
    background-color: #409eff;
    border-color: #409eff;
}
/* 选中后中间的对号,通过旋转45度得到 */
.y-checkbox__input.is-checked .y-checkbox__inner::after {
    transform: rotate(45deg) scale(1);
}
/* 半选中状态 */
.y-checkbox__input.is-indeterminate .y-checkbox__inner {
    background-color: #409eff;
    border-color: #409eff;
}
/* 半选中状态 中间的横杠 */
.y-checkbox__input.is-indeterminate .y-checkbox__inner::after {
    content: none;
}
.y-checkbox__input.is-indeterminate .y-checkbox__inner::before {
    content: '';
    display: inline-block;
    height: 2px;
    background-color: #fff;
    position: absolute;
    top: 5px;
    right: 0;
    left: 0;
    transform: scale(.5);
}
/* 隐藏原生多选框 */
.y-checkbox__original {
    opacity: 0;
    outline: none;
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: -1;
    margin: 0;
}
.y-checkbox__label {
    font-size: 14px;
    display: inline-block;
    padding-left: 10px;
}

总结

  • 该 checkbox 测试,实现v-model、lable属性和change事件
  • checkbox 逻辑跟 radio 有很多相似之处,相似的html结构、判定逻辑
  • checkbox 三种选中状态(注意半选中状态切换到选中状态和取消选中状态)以及其两种使用形式
  • indexOf 与 includes 的区别
  • {}.toString.call(obj) 与 obj.toString() 的区别
  • checkbox 选中状态下中间的对号的生成(伪元素创建,旋转)