Element-ui 之 Checkbox 组件的源码分析

322 阅读6分钟

一. Checkbox 组件提供的属性和事件

1.1 属性

以下是 Checkbox 组件提供的属性:

参数说明类型可选值默认值
value / v-model绑定值string / number / boolean----
label选中状态的值(只有在checkbox-group或者绑定对象类型为array时有效)string / number / boolean----
true-label选中时的值string / number----
false-label没有选中时的值string / number----
disabled是否禁用boolean--false
border是否显示边框boolean--false
sizeCheckbox 的尺寸,仅在 border 为真时有效stringmedium / small / mini--
name原生 name 属性string----
checked当前是否勾选boolean--false
indeterminate设置 indeterminate 状态,只负责样式控制boolean--false

1.2 事件

事件名称说明回调参数
change当绑定值变化时触发的事件更新后的值

二. 按照步骤去实现 Checkbox 组件的属性和事件

  1. 注册组件。
  2. checkbox 的基本 template 部分。
  3. 设置 checkbox 的中文描述。
  4. 设置 checkbox 的绑定值 value/v-model
  5. true-labelfalse-label 属性的处理。
  6. 当前是否勾选 checked 属性的处理。
  7. 禁用 disabled 属性的处理。
  8. 边框 border 属性的处理。
  9. 尺寸 size 属性的处理。
  10.  半选状态 indeterminate 属性的处理。
  11.  原生 name 属性的处理。
  12.  屏幕阅读器相关处理。
  13.  focus 状态的处理。

三. Checkbox 组件属性和事件的具体实现

3.1 注册组件

新建一个 checkbox.vue 文件,设置组件的 namecomponentName 都为 ElCheckbox
main.js 文件里面引入这个组件,并且使用 Vue.component 来全局注册这个组件。

3.2 checkbox 的基本 template 部分

以下是 checkbox 组件基础的 HTML 结构部分:

<template>
  <!-- checkbox 整体用 label 标签包裹 -->
  <label
    class="el-checkbox"
  >
    <!-- checkbox 左侧的点击按钮,用 span 做样式,将 input type=checkbox 放置在 span 标签的下方 -->
    <span class="el-checkbox__input">
      <span class="el-checkbox__inner"></span>
      <input
        class="el-checkbox__original"
        type="checkbox"
      />
    </span>
    <!-- checkbox 右侧的描述 -->
    <span class="el-checkbox__label"></span>
  </label>
</template>
<script>
export default {
  name: 'ElCheckbox',
    
  componentName: 'ElCheckbox',
}
</script>

3.3 设置 checkbox 的中文描述

实现步骤:

  1. 新建一个文件,文件内部引入 checkbox 组件,并且在组件内部写一段中文描述。
<template>
  <div>
    <el-checkbox>备选项</el-checkbox>
  </div>
</template>
<script>
export default {
  
}
</script>
  1. 在组件内部的右侧描述中使用 slot 插槽的形式去接收父组件传过来标签内内容。
<!-- checkbox 右侧的描述 -->
<span class="el-checkbox__label">
  <slot></slot>
</span>

3.4 设置 checkbox 的绑定值 value/v-model

3.4.1 v-model 绑定 Boolean 类型的值

实现步骤:

  1. 使用 checkbox 组件时,用 v-model 绑定一个 Boolean 类型的值,初始值为 false
<template>
  <div>
    <el-checkbox v-model="isCheck">备选项</el-checkbox>
  </div>
</template>
<script>
export default {
  data() {
    return {
      isCheck: false
    }
  }
}
</script>
  1. 组件内部默认用 value 去接收。
props: {
  value: {},
}
  1. data 中定义 selfModel 变量,作为记录组件内部值的变量,该变量默认值为 false
data() {
  return {
    selfModel: false,
  }
},
  1. 定义计算属性 model 作为组件内部 inputv-model 绑定值。
    (1)在 modelgetter 方法里面判断传过来的 value 是否为 undefined,如果不是 undefined 则采用传过来的 value 值,否则采用 selfModel 变量的默认 false 的值。
computed: {
  model: {
    get() {
      return this.value !== undefined ? this.value : this.selfModel;
    },
    ...
  }
},

(2)在 modelsetter 方法里面当值变化时,调用父组件的 input 方法,并且将 selfModel 变量的值设置为改变后的值。

computed: {
  model: {
    ...
    set(val) {
      this.$emit('input', val);
      this.selfModel = val;
    }
  }
},

(3)将组件内部 inputv-model 值绑定为 model 变量。

<span class="el-checkbox__input">
  <span class="el-checkbox__inner"></span>
  <!-- 将 v-model 的值绑定为 model 变量 -->
  <input
    class="el-checkbox__original"
    type="checkbox"
    v-model="model"
  />
</span>

(4)设置组件的选中:设置计算属性 isChecked 判断 model 为 Boolean 类型时的选中结果,并且将 is-checked 的样式添加到 dom 元素上。

computed: {
  isChecked() {
    if ({}.toString.call(this.model) === '[object Boolean]') {
      return this.model;
    }
  }
}

(5)切换选中时让父组件可以监听到 change 事件:input 增加 change 事件监听方法 handleChangehandleChange 方法中使用 emit 让父组件监听到 change 事件。

<input
  class="el-checkbox__original"
  type="checkbox"
  :value="label"
  v-model="model"
  @change="handleChange"
/>
methods: {
  handleChange(ev) {
    let value = ev.target.checked;
    this.$emit('change', value, ev);
  }
}

3.4.2 v-model 绑定 Array 类型的值

v-model 也可以绑定 Array 类型的值,此时需要 checkbox 组件传入 label 属性。

实现步骤:

  1. 使用 checkbox 组件时,传入 label 属性,并且将 v-model 的绑定值改为数组类型。
<template>
  <div>
    <el-checkbox v-model="checkList" :label="labelVal">备选项</el-checkbox>
  </div>
</template>
<script>
export default {
  data() {
    return {
      labelVal: 1,
      checkList: [1]
    }
  }
}
</script>
  1. 使用 props 接收传入的 label 属性。
props: {
  value: {},
}
  1. 将组件内部 inputvalue 值绑定为传入的 label
<input
  class="el-checkbox__original"
  type="checkbox"
  :value="label"
  v-model="model"
/>
  1. 完善组件的右侧描述部分,如果父组件标签内未传入内容,但是 label 属性有值,则展示 label 属性的值。
<!-- checkbox 右侧的描述 -->
<span class="el-checkbox__label" v-if="$slots.default || label">
  <slot></slot>
  <template v-if="!$slots.default">{{label}}</template>
</span>
  1. 修改计算属性 isChecked,支持传入数组的选中。
isChecked() {
  ...
  } else if (Array.isArray(this.model)) {
    return this.model.indexOf(this.label) > -1
  }
}

3.5 true-label 和 false-label 属性的处理

true-label 和 false-label 介绍:

  • true-labelfalse-label 是 Vue 特有的属性,需要和 v-model 配合起来使用。
  • true-label 代表选中时被设置的值,false-label 代表未被选中时被设置的值。

实现步骤:

  1. 父组件传入 true-labelfalse-label 属性,并且将 v-model 改为与 true-label 一致,将组件内部传入的文案也设置为 v-model 绑定的变量。
<template>
  <div>
    <el-checkbox
      v-model="checkVal"
      true-label="选中了"
      false-label="未选中"
    >{{checkVal}}</el-checkbox>
  </div>
</template>
<script>
export default {
  data() {
    return {
      checkVal: "选中了"
    }
  },
}
</script>
  1. props 接收传入的 true-labelfalse-label 属性。
props: {
  trueLabel: [String, Number],
  falseLabel: [String, Number],
}
  1. 修改 input,根据是否传入 true-labelfalse-label 属性,来展示不同的 input。判断如果传入了 true-labelfalse-label,则将 value 属性去掉,增加 true-valuefalse-value 属性。
<input
  v-if="trueLabel || falseLabel"
  class="el-checkbox__original"
  type="checkbox"
  :true-value="trueLabel"
  :false-value="falseLabel"
  v-model="model"
  @change="handleChange"
/>
<input
  v-else
  class="el-checkbox__original"
  type="checkbox"
  :value="label"
  v-model="model"
  @change="handleChange"
/>
  1. 完善计算属性 isChecked,如果传入的不是布尔类型,也不是数组,则判断是否和 true-label 的值一致,如果一致也为选中状态。
computed: {
  isChecked() {
    ...
    } else if (this.model !== null && this.model !== undefined) {
      return this.model === this.trueLabel;
    }
  }
}
  1. 修改 handleChange 方法,如果传入了 true-labelfalse-label 属性,则将返回的 value 值改为 true-labelfalse-label 属性的值。
methods: {
  ...
  handleChange(ev) {
    let value;
    if (ev.target.checked) {
      value = this.trueLabel === undefined ? true : this.trueLabel;
    } else {
      value = this.falseLabel === undefined ? false : this.falseLabel;
    }
    this.$emit('change', value, ev);
  }
}

3.6 当前是否勾选 checked 属性的处理

实现步骤:

  1. 父组件传入 checked 属性。
<template>
  <div>
    <el-checkbox
      v-model="isChecked"
      checked
    >备选项</el-checkbox>
  </div>
</template>
<script>
export default {
  data() {
    return {
      isChecked: false
    }
  },
}
</script>
  1. props 监听父组件传过来的 checked 属性。
props: {
  ...
  checked: Boolean,
  ...
}
  1. 如果 checkedtrue,则改变计算属性 model 的值,如果 model 是数组且不包含 checkedtrue 的选项,则将该选项 pushmodel 数组中,如果不是数组则将 model 优先设置为 true-label 的值,如果 true-label 没有值,则设置为 true
methods: {
  addToStore() {
    if (
      Array.isArray(this.model) &&
      this.model.indexOf(this.label) === -1
    ) {
      this.model.push(this.label);
    } else {
      this.model = this.trueLabel || true;
    }
  },
},
created() {
  this.checked && this.addToStore();
}

3.7 禁用 disabled 属性的处理

实现步骤:

  1. 父组件传入 disabled 属性。
<template>
  <div>
    <el-checkbox
      v-model="isChecked"
      disabled
    >备选项</el-checkbox>
  </div>
</template>
<script>
export default {
  data() {
    return {
      isChecked: false
    }
  },
}
</script>
  1. props 监听父组件传过来的 disabled 属性。
props: {
  ...
  disabled: Boolean,
  ...
}
  1. 计算属性 isDisabled 监听 disabled 的改变。
computed: {
  ...
  isDisabled() {
    return this.disabled;
  }
  ...
}
  1. input 元素增加 disabled 属性 :disabled="isDisabled"
<input
  v-if="trueLabel || falseLabel"
  class="el-checkbox__original"
  type="checkbox"
  :disabled="isDisabled"
  :true-value="trueLabel"
  :false-value="falseLabel"
  v-model="model"
  @change="handleChange"
/>
<input
  v-else
  class="el-checkbox__original"
  type="checkbox"
  :disabled="isDisabled"
  :value="label"
  v-model="model"
  @change="handleChange"
/>
  1. el-checkboxel-checkbox__input 增加 disabled 属性的样式。
<label
  class="el-checkbox"
  :class="[
    ...
    { 'is-checked': isChecked }
  ]"
>
  <!-- checkbox 左侧的点击按钮,用 span 做样式,将 input type=checkbox 放置在 span 标签的下方 -->
  <span
    class="el-checkbox__input"
    :class="{
      ...
      'is-checked': isChecked,
    }"
  >
    ...
  </span>
  ...
</label>

3.8 边框 border 属性的处理

实现步骤:

  1. 父组件传入 border 属性。
<template>
  <div>
    <!-- 传入 border 属性 -->
    <el-checkbox
      v-model="isChecked"
      border
    >备选项</el-checkbox>
  </div>
</template>
<script>
export default {
  data() {
    return {
      isChecked: false
    }
  },
}
</script>
  1. props 监听父组件传入的 border 属性。
props: {
  border: Boolean,                
}
  1. el-checkbox 上面增加 border 属性的样式。
<label
  class="el-checkbox"
  :class="[
    ...
    { 'is-bordered': border },
  ]"
>
  ...
</label>

3.9 尺寸 size 属性的处理

实现步骤:

  1. 父组件传入 size 属性。
<template>
  <div>
    <!-- 传入 size="mini" -->
    <el-checkbox
      v-model="isChecked"
      border
      size="mini"
    >备选项</el-checkbox>
  </div>
</template>
<script>
export default {
  data() {
    return {
      isChecked: false
    }
  },
}
</script>
  1. props 监听传入的 size 属性。
props: {
  size: String
}
  1. 设置计算属性 checkboxSize 监听传入的 size 属性的改变。
computed: {
  checkboxSize() {
    return this.size;
  }
}
  1. bordersize 两个属性均存在的情况下,将 size 的样式加在 el-checkbox 元素上。
<label
  class="el-checkbox"
  :class="[
    border && checkboxSize ? 'el-checkbox--' + checkboxSize : '',
    ...
  ]"
>
  ...
</label>

3.10 半选状态 indeterminate 属性的处理

半选 indeterminate 是指中间有一个横线的半选状态样式,多用于其下级有选中和未选中两种,则父级展示半选状态。

实现步骤:

  1. 父组件传入 indeterminate 属性。
<template>
  <div>
    <el-checkbox
      v-model="isChecked"
      :indeterminate="isIndeterminate"
    >备选项</el-checkbox>
  </div>
</template>
<script>
export default {
  data() {
    return {
      isChecked: false,
      isIndeterminate: true
    }
  },
}
</script>
  1. props 监听父组件传入的 indeterminate 属性。
props: {
  ...
  indeterminate: Boolean,
  ...
}
  1. el-checkbox__input 上面增加 indeterminate 样式。
<!-- checkbox 左侧的点击按钮,用 span 做样式,将 input type=checkbox 放置在 span 标签的下方 -->
<span
  class="el-checkbox__input"
  :class="{
    ...
    'is-indeterminate': indeterminate,
  }"
>
  ...
</span>

3.11 原生 name 属性的处理

实现步骤:

  1. 监听父组件传过来的 name 属性。
props: {
  name: String
}
  1. name 属性绑定在 checkbox 上。
<input
  v-if="trueLabel || falseLabel"
  class="el-checkbox__original"
  type="checkbox"
  :name="name"
  :disabled="isDisabled"
  :true-value="trueLabel"
  :false-value="falseLabel"
  v-model="model"
  @change="handleChange"
/>
<input
  v-else
  class="el-checkbox__original"
  type="checkbox"
  :name="name"
  :disabled="isDisabled"
  :value="label"
  v-model="model"
  @change="handleChange"
/>

3.12 屏幕阅读器相关处理

aria-controls 属性介绍:

aria-controls是一种用于描述HTML元素之间关系的属性,它用来指定一个元素与另一个元素之间的关系。使用aria-controls属性时,必须指定需要控制的元素的 ID,而且这个 ID 必须存在于页面中。

例如:在 checkbox 组件的半选按钮上面增加 aria-controls 属性,控制的其他按钮的范围外层增加 id 属性。

实现步骤:

  1. props 接收 idcontrols 属性,用于增加 aria-controls 属性的值。
props: {
  ...
  id: String,
  controls: String,
  ...
}
  1. 将传入的 id 属性增加到组件根元素上面。
<label
  class="el-checkbox"
  ...
  :id="id"
>
</label>
  1. 给组件根元素增加 aria-controls 属性。
mounted() {
  if (this.indeterminate) {
    this.$el.setAttribute('aria-controls', this.controls);
  }
},
  1. 增加 role 属性、aria- 前缀属性、tabIndex 属性。

(1)给最外层 label 元素增加如下属性,表示如果是半选则增加 roletabIndexaria-checked 属性。

<label
  class="el-checkbox"
  ...
  :tabindex="indeterminate ? 0 : false"
  :role="indeterminate ? 'checkbox' : false"
  :aria-checked="indeterminate ? 'mixed' : false"
>
</label>

(2)给内层的 input 元素增加属性,表明如果是半选,对屏幕阅读器隐藏 input

<input
  v-if="trueLabel || falseLabel"
  class="el-checkbox__original"
  ...
  :aria-hidden="indeterminate ? 'true' : 'false'"
/>
<input
  v-else
  class="el-checkbox__original"
  ...
  :aria-hidden="indeterminate ? 'true' : 'false'"
  ...
/>

注意:

  1. 和源代码不同的地方是,源代码中将 role 等属性加到了包裹 inputspan 标签上,但是经查阅 aria-controls 属性,使用该属性的元素必须具有与其相应的角色与状态属性,则将 role 等属性放到了最外层的 label 标签上面。

  2. controls 属性和 id 属性在 Element-uicheckbox 组件文档中并没有写明这两个属性,是在源代码中发现的可以支持这两个属性。

3.13 focus 状态的处理

增加 is-focusclass,用于样式处理。

实现步骤:

  1. data 下设置 focus 变量,初始值为 false
data() {
  return {
    ...
    focus: false,
  }
},
  1. 在触发 inputfocusblur 时,切换 focus 变量。
<input
  ...
  @focus="focus = true"
  @blur="focus = false"
/>
<input
  ...
  @focus="focus = true"
  @blur="focus = false"
/>

el-checkbox__input 增加 focus 样式。

<!-- checkbox 左侧的点击按钮,用 span 做样式,将 input type=checkbox 放置在 span 标签的下方 -->
<span
  class="el-checkbox__input"
  :class="{
    ...
    'is-focus': focus
  }"
>
  ...
</span>

相关 focus 样式,是在 focus 时出现的蓝色边框。

.el-checkbox__inner {
  border-color: $--checkbox-input-border-color-hover;
}

四. 整体代码详解

包含 CheckboxCheckbox-group 组件以及 Form 组件相互作用部分。

<template>
  <!-- checkbox 整体用 label 标签包裹 -->
  <label
    class="el-checkbox"
    :class="[
      border && checkboxSize ? 'el-checkbox--' + checkboxSize : '',
      { 'is-disabled': isDisabled },
      { 'is-bordered': border },
      { 'is-checked': isChecked }
    ]"
    :id="id"
    :tabindex="indeterminate ? 0 : false"
    :role="indeterminate ? 'checkbox' : false"
    :aria-checked="indeterminate ? 'mixed' : false"
  >
    <!-- checkbox 左侧的点击按钮,用 span 做样式,将 input type=checkbox 放置在 span 标签的下方 -->
    <span
      class="el-checkbox__input"
      :class="{
        'is-disabled': isDisabled,
        'is-checked': isChecked,
        'is-indeterminate': indeterminate,
        'is-focus': focus
      }"
    >
      <span class="el-checkbox__inner"></span>
      <input
        v-if="trueLabel || falseLabel"
        class="el-checkbox__original"
        type="checkbox"
        :aria-hidden="indeterminate ? 'true' : 'false'"
        :name="name"
        :disabled="isDisabled"
        :true-value="trueLabel"
        :false-value="falseLabel"
        v-model="model"
        @change="handleChange"
        @focus="focus = true"
        @blur="focus = false"
      />
      <input
        v-else
        class="el-checkbox__original"
        type="checkbox"
        :aria-hidden="indeterminate ? 'true' : 'false'"
        :name="name"
        :disabled="isDisabled"
        :value="label"
        v-model="model"
        @change="handleChange"
        @focus="focus = true"
        @blur="focus = false"
      />
    </span>
    <!-- checkbox 右侧的描述 -->
    <span class="el-checkbox__label" v-if="$slots.default || label">
      <slot></slot>
      <template v-if="!$slots.default">{{label}}</template>
    </span>
  </label>
</template>
<script>
import Emitter from '../../utils/mixins/emitter';

export default {
  name: 'ElCheckbox',
  
  mixins: [Emitter],
  
  inject: {
    elForm: {
      default: ''
    },
    elFormItem: {
      default: ''
    }
  },
    
  componentName: 'ElCheckbox',
  
  data() {
    return {
      selfModel: false, // 组件内部的值
      focus: false, // focus 状态
      isLimitExceeded: false // 是否超出可选数量限制
    }
  },
  
  computed: {
    model: {
      get() {
        // 判断是否是按钮组,如果是按钮组,则获取按钮组的值,否则获取 value,如果 value 是 undefined,则获取组件内部的 selfModel 值
        return this.isGroup
          ? this.store : this.value !== undefined
            ? this.value : this.selfModel;
      },
      set(val) {
        // 先来判断是否是按钮组
        if (this.isGroup) {
          // 是按钮组的情况下
          // 如果当前选中的数量小于最小的限制数量,则将 isLimitExceeded 设置为 true,表示已选数量与限制数量不符
          (this._checkboxGroup.min !== undefined &&
            val.length < this._checkboxGroup.min &&
            (this.isLimitExceeded = true));
          // 如果当前选中的数量大于最大的限制数量,则将 isLimitExceeded 设置为 true,表示已选数量与限制数量不符
          (this._checkboxGroup.max !== undefined &&
            val.length > this._checkboxGroup.max &&
            (this.isLimitExceeded = true));
          // 如果 isLimitExceeded 为 false,则出发 checkbox-grouo 的 input 事件
          this.isLimitExceeded === false &&
          this.dispatch('ElCheckboxGroup', 'input', [val]);
        } else {
          // 不是按钮组的情况,触发父组件的 input 事件,并且将组件内部的 selfModel 变量设置为当前的 value 值
          this.$emit('input', val);
          this.selfModel = val;
        }
      }
    },
    // 是否选中
    isChecked() {
      // 判断 model 值的类型,根据类型判断是否选中
      if ({}.toString.call(this.model) === '[object model]') {
        // 如果是布尔类型,则直接返回 model 的值用来判断是否选中
        return this.model;
      } else if (Array.isArray(this.model)) {
        // 如果是数组,则判断当前 model 数组是否包含当前 label 的值,如果包含,则选中
        return this.model.indexOf(this.label) > -1
      } else if (this.model !== null && this.model !== undefined) {
        // 如果不是布尔类型,也不是数组类型,且 model 不为 null 和 undefined
        // 则判断 model 与 trueLabel(trueLabel是选中时显示的值)是否相等,如果相等,则选中
        return this.model === this.trueLabel;
      }
    },
    // 判断是否被包含在了按钮组里面,如果外层有 checkbox-group 组件,则说明被包含在按钮组里面
    isGroup() {
      let parent = this.$parent;
      while (parent) {
        if (parent.$options.componentName !== 'ElCheckboxGroup') {
          parent = parent.$parent;
        } else {
          this._checkboxGroup = parent;
          return true;
        }
      }
      return false;
    },
    
    store() {
      // 如果是按钮组,则返回按钮组的 value,否则返回组件的 value
      return this._checkboxGroup ? this._checkboxGroup.value : this.value;
    },
    // 判断是否由于不符合可选数量造成禁用
    isLimitDisabled() {
      const { max, min } = this._checkboxGroup;
      // 当已选数量大于等于最大限制数量时,禁用按钮组中没选中的 checkbox
      // 当已选数量小于等于最小限制数量时,禁用按钮组中已选中的 checkbox
      return !!(max || min) &&
        (this.model.length >= max && !this.isChecked) ||
        (this.model.length <= min && this.isChecked);
    },
    // disabled 计算
    isDisabled() {
      // 复选框组优先级:group > 组件内部disabled > Form > 可选数量限制disabled
      // 复选框优先级:组件内部disabled > Form
      return this.isGroup
        ? this._checkboxGroup.disabled || this.disabled || (this.elForm || {}).disabled  || this.isLimitDisabled
        : this.disabled || (this.elForm || {}).disabled;
    },
    // 获取 Form-Item 组件的 elFormItemSize 变量
    _elFormItemSize() {
      return (this.elFormItem || {}).elFormItemSize;
    },
    // Checkbox 的尺寸计算
    checkboxSize() {
      // 设置变量 temCheckboxSize,优先获取组件内部的 size,再获取 Form-Item 组件的 elFormItemSize
      const temCheckboxSize = this.size || this._elFormItemSize
      // 复选框组优先级:group-size > 组件内部size > Form-Item > Form
      // 复选框优先级:组件内部size > Form-Item > Form
      return this.isGroup
        ? this._checkboxGroup.checkboxGroupSize || temCheckboxSize
        : temCheckboxSize;
    }
  },
  
  props: {
    value: {},
    label: {},
    indeterminate: Boolean,
    disabled: Boolean,
    checked: Boolean,
    name: String,
    trueLabel: [String, Number],
    falseLabel: [String, Number],
    id: String,
    controls: String,
    border: Boolean,
    size: String
  },
  
  methods: {
    // 如果最开始是选中状态,则将值存储到 model 变量中
    addToStore() {
      if (
        Array.isArray(this.model) &&
        this.model.indexOf(this.label) === -1
      ) {
        // model 为数组,并且 model 不包含 label 的值,则将 label 的值 push 到 model 数组中
        this.model.push(this.label);
      } else {
        // model 不是数组,如果有 true-label,则设置为 true-label 的值,否则设置为 true
        this.model = this.trueLabel || true;
      }
    },
    // 触发 change 事件
    handleChange(ev) {
      // 如果不符合可选的数量限制,则直接 return
      if (this.isLimitExceeded) return;
      let value;
      if (ev.target.checked) {
        // 如果选中了,存在 true-label 则设置为 true-label,否则设置为 true
        value = this.trueLabel === undefined ? true : this.trueLabel;
      } else {
        // 如果没有选中,存在 false-label 则设置为 false-label,否则设置为 false
        value = this.falseLabel === undefined ? false : this.falseLabel;
      }
      // 触发父组件的 change 事件
      this.$emit('change', value, ev);
      this.$nextTick(() => {
        if (this.isGroup) {
          // 如果被包含在按钮组里面,则触发按钮组的 change 事件
          this.dispatch('ElCheckboxGroup', 'change', [this._checkboxGroup.value]);
        }
      });
    }
  },
  
  created() {
    // 如果最开始是选中状态,则将值存储到 model 变量中
    this.checked && this.addToStore();
  },
  
  mounted() {
    if (this.indeterminate) {
      // 屏幕阅读器的处理,如果是半选状态,则增加 aria-controls 属性
      this.$el.setAttribute('aria-controls', this.controls);
    }
  },
  
  watch: {
    value(value) {
      // 监听 value 值的变化,当 value 值改变时,向 Form-Item 组件派发 el.form.change 自定义事件
      this.dispatch('ElFormItem', 'el.form.change', value);
    }
  }
}
</script>