I. Component introduction

Website links: the Checkbox component | Element (gitee. IO)

The Checkbox component is one of the most commonly used components on a daily basis and is used by users to check options.

1.1 attributes

1.1.1 Value binding correlation

  • Model-value/V-model: string/number/ Boolean type, binding value;
  • Label: string/number/Boolean/object, value when selected, incheckbox-groupOr the binding object type isarrayAvailable at the time;
  • True-lable: string/number, the value when selected.
  • False -label: string/number, if not selected.
  • Checked: Boolean Indicates the Boolean type.

1.1.2 Display related

  • Disabled: Boolean indicates whether to disable.
  • Border: Boolean, whether to display a border;
  • Name: string, native name attribute;
  • Size: string. This parameter is optional. This parameter is valid only in the border statemedium/small/mini;
  • Indeterminate: Boolean, set semi-selected state, set style only, default false;

1.2 event

  • “Change” : triggered when the binding value changes.

Second, source code analysis

2.1 the template

<template>// Use the lable label<label
    :id="id"
    class="el-checkbox"
    :class="[ border && checkboxSize ? 'el-checkbox--' + checkboxSize : '', { 'is-disabled': isDisabled }, { 'is-bordered': border }, { 'is-checked': isChecked } ]"
    :aria-controls="indeterminate ? controls : null"
  >// Select the box section<span
      class="el-checkbox__input"
      :class="{ 'is-disabled': isDisabled, 'is-checked': isChecked, 'is-indeterminate': indeterminate, 'is-focus': focus }"
      :tabindex="indeterminate ? 0 : false"
      :role="indeterminate ? 'checkbox' : false"
      :aria-checked="indeterminate ? 'mixed' : false"
    >// Used to control checked/unchecked styles<span class="el-checkbox__inner"></span>// With trueLable/falseLabel, add custom true-value/false-value attributes to input<input
        v-if="trueLabel || falseLabel"
        v-model="model"
        :checked="isChecked"
        class="el-checkbox__original"
        type="checkbox"
        :aria-hidden="indeterminate ? 'true' : 'false'"
        :name="name"
        :disabled="isDisabled"
        :true-value="trueLabel"
        :false-value="falseLabel"
        @change="handleChange"
        @focus="focus = true"
        @blur="focus = false"
      >
      <input
        v-else
        v-model="model"
        class="el-checkbox__original"
        type="checkbox"
        :aria-hidden="indeterminate ? 'true' : 'false'"
        :disabled="isDisabled"
        :value="label"
        :name="name"
        @change="handleChange"
        @focus="focus = true"
        @blur="focus = false"
      >
    </span>// The text part<span v-if="$slots.default || label" class="el-checkbox__label">
      <slot></slot>
      <template v-if=! "" $slots.default">{{ label }}</template>
    </span>
  </label>
</template>
Copy the code

2.2 the script

// checkbox.vue
// Some core code
import { useCheckbox } from './useCheckbox'

export default defineComponent({
  setup(props) {
    // Call the useCheckbox method
    return useCheckbox(props)
  },
})
Copy the code
// useCheckbox.ts

export const useCheckboxGroup = () = > {
  const ELEMENT = useGlobalConfig()
  // checkbox Inject data when used in form/checkbox-group
  const elForm = inject(elFormKey, {} as ElFormContext)
  const elFormItem = inject(elFormItemKey, {} as ElFormItemContext)
  const checkboxGroup = inject<ICheckboxGroupInstance>('CheckboxGroup', {})
  // Whether the mode is group
  const isGroup = computed(() = >checkboxGroup && checkboxGroup? .name ==='ElCheckboxGroup')
  / / form - the size of the item
  const elFormItemSize = computed(() = > {
    return elFormItem.size
  })
  return {
    isGroup,
    checkboxGroup,
    elForm,
    ELEMENT,
    elFormItemSize,
    elFormItem,
  }
}

// 
const useModel = (props: ICheckboxProps) = > {
  const selfModel = ref(false)
  const { emit } = getCurrentInstance()
  const { isGroup, checkboxGroup } = useCheckboxGroup()
  const isLimitExceeded = ref(false)
  // In group mode, set the group binding value; For standalone use, take the passed modelValue value
  const store = computed(() = >checkboxGroup ? checkboxGroup.modelValue? .value : props.modelValue)// Sets the calculation properties of getter and setter
  const model = computed({
    get() {
      return isGroup.value
        ? store.value
        : props.modelValue ?? selfModel.value
    },

    set(val: unknown) {
      if (isGroup.value && Array.isArray(val)) {
        // In chebox-group mode, data is in array format
        isLimitExceeded.value = false
        // Check whether the range of optional numbers set by checbox-group is exceeded
        if(checkboxGroup.min ! = =undefined && val.length < checkboxGroup.min.value) {
          isLimitExceeded.value = true
        }
        if(checkboxGroup.max ! = =undefined && val.length > checkboxGroup.max.value) {
          isLimitExceeded.value = true
        }

        isLimitExceeded.value === false&& checkboxGroup? .changeEvent? .(val) }else {
        // Emit the V-model event, update:modelValue
        emit(UPDATE_MODEL_EVENT, val)
        selfModel.value = val as boolean}}})return {
    model,
    isLimitExceeded,
  }
}

/ / the checkbox state
const useCheckboxStatus = (props: ICheckboxProps, { model }: PartialReturnType<typeof useModel>) = > {
  const { isGroup, checkboxGroup, elFormItemSize, ELEMENT } = useCheckboxGroup()
  const focus = ref(false)
  const size = computed<string | undefined> (() = >checkboxGroup? .checkboxGroupSize? .value || elFormItemSize.value || ELEMENT.size)// Whether it is selected
  const isChecked = computed(() = > {
    const value = model.value
    // Judgment processing under various data types
    if (toTypeString(value) === '[object Boolean]') {
      // Boolean that returns model.value directly
      return value
    } else if (Array.isArray(value)) {
      // Check whether the array contains the lable of the current checkbox
      return value.includes(props.label)
    } else if(value ! = =null&& value ! = =undefined) {
      // Other data types to determine if they are equal to the trueLabel passed in
      return value === props.trueLabel
    }
  })
  // size: passed size > form-item size > global configuration size
  const checkboxSize = computed(() = > {
    const temCheckboxSize = props.size || elFormItemSize.value || ELEMENT.size
    returnisGroup.value ? checkboxGroup? .checkboxGroupSize? .value || temCheckboxSize : temCheckboxSize })return {
    isChecked,
    focus,
    size,
    checkboxSize,
  }
}

// Whether to disable
const useDisabled = (
  props: ICheckboxProps,
  { model, isChecked }: PartialReturnType<typeof useModel> & PartialReturnType<typeof useCheckboxStatus>,
) = > {
  const { elForm, isGroup, checkboxGroup } = useCheckboxGroup()
  // Disable judgment due to optional quantity range
  const isLimitDisabled = computed(() = > {
    constmax = checkboxGroup.max? .valueconstmin = checkboxGroup.min? .valuereturn!!!!! (max || min) && (model.value.length >= max && ! isChecked.value) || (model.value.length <= min && isChecked.value) })// In group mode: Disabeld of the group > Disabled of itself > Disabled of number limit
  // Use alone: prop disabled > form disabled
  const isDisabled = computed(() = > {
    const disabled = props.disabled || elForm.disabled
    returnisGroup.value ? checkboxGroup.disabled? .value || disabled || isLimitDisabled.value : props.disabled || elForm.disabled })return {
    isDisabled,
    isLimitDisabled,
  }
}

// 
const setStoreValue = (props: ICheckboxProps, { model }: PartialReturnType<typeof useModel>) = > {
  function addToStore() {
    If the array does not contain the label of the current checkbox, push the label of the current checkbox into the array
    if (
      Array.isArray(model.value) && ! model.value.includes(props.label) ) { model.value.push(props.label) }else {
      model.value = props.trueLabel || true}}// addToStore is executed when selected
  props.checked && addToStore()
}

/ / event
const useEvent = (props: ICheckboxProps, { isLimitExceeded }: PartialReturnType<typeof useModel>) = > {
  const { elFormItem } = useCheckboxGroup()
  const { emit } = getCurrentInstance()
  // change handler function
  function handleChange(e: InputEvent) {
    if (isLimitExceeded.value) return
    const target = e.target as HTMLInputElement
    const value = target.checked
      ? props.trueLabel ?? true
      : props.falseLabel ?? false
    // Throw the change event outward
    emit('change', value, e)
  }

  watch(() = > props.modelValue, val= >{ elFormItem.formItemMitt? .emit('el.form.change', [val])
  })

  return {
    handleChange,
  }
}

// useCheckbox is called in checkbox.vue
export const useCheckbox = (props: ICheckboxProps) = > {
  const { model, isLimitExceeded } = useModel(props)
  const { focus, size, isChecked, checkboxSize } = useCheckboxStatus(props, { model })
  const { isDisabled } = useDisabled(props, { model, isChecked })
  const { handleChange } = useEvent(props, { isLimitExceeded })

  setStoreValue(props, { model })

  return {
    isChecked,
    isDisabled,
    checkboxSize,
    model,
    handleChange,
    focus,
    size,
  }
}
Copy the code

2.3 summarize

  1. checkbox.vueThe script part of the script is mainly calleduseCheckboxMethod, rather than writing all property methods in the setup function, this is more elegant and concise, divided functions by function, corresponding data and methods in the same function, also introduced by VUe3composition-apiThe original intention of the formula;
  2. The checkbox’s model value can be used alonestring/boolean/numberType; But in thecheckbox-groupIn the checkbox-group mode, the model value is in the form of an array, and the linkage between a single Checkbox and a global Checkbox-group is achieved through getter and setter methods.