从零搭建Vue3.0组件库之checkbox组件

一.设计组件属性

export interface ICheckboxProps {
    name?: string, // input中name属性
    label?: string | boolean | number, // v-model为array时使用
    modelValue: string | boolean | number, // 绑定的值
    indeterminate?: boolean // 是否半选
    disabled?: boolean // 禁用
    checked?: boolean // 是否选中
}
1
2
3
4
5
6
7
8

二.编写checkbox组件

<template>
  <label class="z-checkbox">
    <span class="z-checkbox__input">
      <input
        type="checkbox"
        :value="label"
        :disabled="disabled"
      />
    </span>
    <!-- 没有默认 有label -->
    <span v-if="$slots.default || label" class="z-checkbox__label">
      <slot></slot>
      <template v-if="!$slots.default">{{ label }}</template>
    </span>
  </label>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import { useCheckbox } from "./useCheckbox";
export default defineComponent({
  name: "ZCheckbox",
  props: {
    name: {
      // checkbox name属性
      type: String,
    },
    modelValue: {
      // input绑定的值
      type: [Boolean, Number, String],
    },
    label: {
      // 选中状态的值
      type: [Boolean, Number, String],
    },
    indeterminate: Boolean, // 半选
    disabled: Boolean, // 禁用
    checked: Boolean, // 是否选中
  }
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

1.实现checkbox双向绑定

<input
       type="checkbox"
       :value="label"
       :disabled="disabled"
       v-model="model"  // 双向绑定的字段
       :checked="isChecked" // input的状态
       @change="handleChange" // 用于将checked字段传递给用户
/>
1
2
3
4
5
6
7
8
const useModel = (props: ICheckboxProps) => {
    const { emit } = getCurrentInstance();
    const model = computed({
        get() {
            return props.modelValue
        },
        set(val: unknown) {
            emit('update:modelValue', val);
        }
    });
    return model;
}
const useCheckboxStatus = (props: ICheckboxProps, model) => {
    const isChecked = computed(() => {
        const value = model.value;
        return value;
    });
    return isChecked
}
const useEvent = () => {
    const { emit } = getCurrentInstance()
    // checkbox修改事件
    function handleChange(e: InputEvent) {
        const target = e.target as HTMLInputElement;
        const value = target.checked ? true : false; // 获取checked属性,触发修改逻辑
        emit('change', value)
    }
    return handleChange
}
export function useCheckbox(props: ICheckboxProps) {
    // 1.实现用于双向绑定的model属性
    const model = useModel(props);
    const isChecked = useCheckboxStatus(props, model);
    const handleChange = useEvent();
    return {
        model,
        isChecked,
        handleChange
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

2.实现半选功能

const inputRef = ref<HTMLInputElement>(null)
function indeterminate(val) {
     inputRef.value.indeterminate = val;
}
watch(() => props.indeterminate, indeterminate)
onMounted(()=>{ // 默认加载完毕后
    indeterminate(props.indeterminate)
});
1
2
3
4
5
6
7
8

三.编写Checkbox-Group组件

checkbox-group组件的功能主要是将数据同步给checkbox组件中

<template>
  <div class="z-checkbox-group">
    <slot></slot>
  </div>
</template>
<script lang="ts">
import { computed, defineComponent, provide, toRefs } from "vue";

export default defineComponent({
  name: "ZCheckboxGroup",
  props: {
    modelValue: {
      type: Array,
    },
    disabled: Boolean,
  },
  emits:['change','update:modelValue'],
  setup(props, ctx) {
    const modelValue = computed(()=>props.modelValue);
    const changeEvent = (val) => { // 此事件用于子同步数据到checkbox-group中
      ctx.emit("update:modelValue", val);
      ctx.emit('change',val)
    };
    provide("CheckBoxGroup", { // 注入数据
      name: "ZCheckBoxGroup",
      modelValue,
      changeEvent,
    });
  },
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

注入实例时编写实例类型

export interface ICheckboxGroupInstance {
    modelValue?: ComputedRef, // 绑定的值
    name?: string
    changeEvent?: (...args: any[]) => any // 修改事件
}
1
2
3
4
5
const useCheckboxGroup = () => {
    const checkboxGroup = inject<ICheckboxGroupInstance>('CheckBoxGroup', {});
    const isGroup = computed(() => checkboxGroup.name == 'ZCheckBoxGroup')
    return {
        isGroup,
        checkboxGroup
    }
}
const useModel = (props: ICheckboxProps) => { // 1.针对checkbox-group处理数据
    const { emit } = getCurrentInstance();
    const { isGroup, checkboxGroup } = useCheckboxGroup(); // 针对checkbox组特殊处理
    const store = computed(() => checkboxGroup ? checkboxGroup.modelValue?.value : props.modelValue); // 将父组件v-model数据获取到
    const model = computed({
        get() {
            return isGroup.value ? store.value : props.modelValue
        },
        set(val: unknown) {
            if (isGroup.value) { // 如果是checkbox 组让父级处理
                checkboxGroup.changeEvent(val)
            } else {
                emit('update:modelValue', val);
            }
        }
    });
    return model;
}
const useCheckboxStatus = (props: ICheckboxProps, model) => { // 2.针对label来处理选中逻辑
    const isChecked = computed(() => {
        const value = model.value;
        if(typeof value == 'boolean'){
            return value;
        }else if(Array.isArray(value)){ // 如果是数组 看是否包含这一项,来确定checked的值
            return value.includes(props.label)
        }
        return value;
    });
    return isChecked
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38