一、组件介绍
官网链接:Checkbox 组件 | Element (gitee.io)
Checkbox组件是日常最为常用的组件之一,用于用户勾选选项。
1.1 属性
1.1.1 值绑定相关
- model-value / v-model: string/number/boolean类型,绑定的值;
- label: string / number / boolean / object,选中时的值,在
checkbox-group或者绑定对象类型为array时有效; - true-lable: string / number, 选中时的值;
- false-label: string / number, 未选中时的值;
- checked: boolean类型,当前是否勾选,默认false;
1.1.2 展示相关
- disabled: boolean类型,是否禁用;
- border: boolean类型,是否显示边框;
- name: string类型,原生name属性;
- size: string类型,仅在border状态下生效,可选值
medium/small/mini; - indeterminate: boolean类型,设置半选状态,仅设置样式,默认false;
1.2 事件
- change: 绑定值发生变化时触发;
二、源码分析
2.1 template
<template>
// 外层采用lable标签
<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"
>
// 选择框部分
<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"
>
// 用于控制选中/未选中样式
<span class="el-checkbox__inner"></span>
// 有trueLable/falseLabel时,给input添加自定义属性true-value/false-value
<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>
// 文字部分
<span v-if="$slots.default || label" class="el-checkbox__label">
<slot></slot>
<template v-if="!$slots.default">{{ label }}</template>
</span>
</label>
</template>
2.2 script
// checkbox.vue
// 部分核心代码
import { useCheckbox } from './useCheckbox'
export default defineComponent({
setup(props) {
// 调用useCheckbox方法
return useCheckbox(props)
},
})
// useCheckbox.ts
export const useCheckboxGroup = () => {
const ELEMENT = useGlobalConfig()
// checkbox用于form/checkbox-group中时,通过inject注入数据
const elForm = inject(elFormKey, {} as ElFormContext)
const elFormItem = inject(elFormItemKey, {} as ElFormItemContext)
const checkboxGroup = inject<ICheckboxGroupInstance>('CheckboxGroup', {})
// 是否是group模式
const isGroup = computed(() => checkboxGroup && checkboxGroup?.name === 'ElCheckboxGroup')
// form-item的size
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)
// group模式下,取group绑定值;独立使用情况下,取传入的modelValue值
const store = computed(() => checkboxGroup ? checkboxGroup.modelValue?.value : props.modelValue)
//设置了getter和setter的计算属性
const model = computed({
get() {
return isGroup.value
? store.value
: props.modelValue ?? selfModel.value
},
set(val: unknown) {
if (isGroup.value && Array.isArray(val)) {
// chebox-group模式下,数据是数组格式的
isLimitExceeded.value = false
// 判断是否超过checbox-group设定的可选个数范围
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 {
// 发射v-model事件,即update:modelValue
emit(UPDATE_MODEL_EVENT, val)
selfModel.value = val as boolean
}
},
})
return {
model,
isLimitExceeded,
}
}
// checkbox状态
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)
// 是否选中
const isChecked = computed(() => {
const value = model.value
// 各种数据类型下的判断处理
if (toTypeString(value) === '[object Boolean]') {
// boolean类型,直接返回model.value
return value
} else if (Array.isArray(value)) {
// checkbox-group模式下,值是数组,判断数组是否包含当前checkbox的lable
return value.includes(props.label)
} else if (value !== null && value !== undefined) {
// 其他数据类型,判断是否与传入的trueLabel相等
return value === props.trueLabel
}
})
// size:传入的size > form-item的size > 全局配置的size
const checkboxSize = computed(() => {
const temCheckboxSize = props.size || elFormItemSize.value || ELEMENT.size
return isGroup.value
? checkboxGroup?.checkboxGroupSize?.value || temCheckboxSize
: temCheckboxSize
})
return {
isChecked,
focus,
size,
checkboxSize,
}
}
// 是否禁用
const useDisabled = (
props: ICheckboxProps,
{ model, isChecked }: PartialReturnType<typeof useModel> & PartialReturnType<typeof useCheckboxStatus>,
) => {
const { elForm, isGroup, checkboxGroup } = useCheckboxGroup()
// 由于可选数量范围导致的禁用判断
const isLimitDisabled = computed(() => {
const max = checkboxGroup.max?.value
const min = checkboxGroup.min?.value
return !!(max || min) && (model.value.length >= max && !isChecked.value) ||
(model.value.length <= min && isChecked.value)
})
// group模式下:group的disabeld > 自身的disabled > 数量限制的disabled
// 单独使用:prop的disabled > form的disabled
const isDisabled = computed(() => {
const disabled = props.disabled || elForm.disabled
return isGroup.value
? checkboxGroup.disabled?.value || disabled || isLimitDisabled.value
: props.disabled || elForm.disabled
})
return {
isDisabled,
isLimitDisabled,
}
}
//
const setStoreValue = (props: ICheckboxProps, { model }: PartialReturnType<typeof useModel>) => {
function addToStore() {
// group模式,数组中不包含当前checkbox的label,则将当前checkbox的label push到数组中
if (
Array.isArray(model.value) &&
!model.value.includes(props.label)
) {
model.value.push(props.label)
} else {
model.value = props.trueLabel || true
}
}
// 选中时执行addToStore
props.checked && addToStore()
}
// 事件
const useEvent = (props: ICheckboxProps, { isLimitExceeded }: PartialReturnType<typeof useModel>) => {
const { elFormItem } = useCheckboxGroup()
const { emit } = getCurrentInstance()
// change处理函数
function handleChange(e: InputEvent) {
if (isLimitExceeded.value) return
const target = e.target as HTMLInputElement
const value = target.checked
? props.trueLabel ?? true
: props.falseLabel ?? false
// 向外抛出change事件
emit('change', value, e)
}
watch(() => props.modelValue, val => {
elFormItem.formItemMitt?.emit('el.form.change', [val])
})
return {
handleChange,
}
}
// useCheckbox在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,
}
}
2.3 总结
checkbox.vue的script部分主要是调用useCheckbox方法,相对于将所有属性方法都写在setup函数中,这种写法更优雅简洁,按功能划分函数,相应的数据和方法在同一个函数中,也是vue3推出的composition-api式写法的初衷;- checkbox的model值在单独使用时可以是
string/boolean/number类型;但是在checkbox-group模式下,被设置为checkbox-group的model值,是数组形式,通过computed的getter和setter方法做到了单个checkbox和全局checkbox-group的联动;