Source code: address

Since I’ve been using Vue3 lately, I’ve tried to write UI component samples on my own, based on the “use it or lose it” principle. The initial selection was the checkbox component. Checkbox component is usually used quite a high frequency of a component, we now step by step to improve their component.

start

Define components first

< ANI - the checkbox > options < / ANI - the checkbox >Copy the code

Create the component checkbox. vue file

<template> <label class="checkbox-wrap" > <input type="checkbox" v-model="model" /> < I class="check-icon">✓</ I > <slot></slot> </label> </template>Copy the code

Input [type= Checkbox]: Checked Checkbox; input[type= Checkbox]:checked

<style lang="less" scoped>
@color: # 333;
@activeColor: #409eff;
.checkbox-wrap {
  margin-right: 15px;
  font-size: 14px;
  user-select: none; cursor: pointer;
  transition: all 0.3 s cubic-bezier(0.645.0.045.0.355.1);
  .check-icon {
    margin-right: 5px;
    font-size: 14px;
    font-style: normal;
    display: inline-block;
    width: 14px;
    height: 14px;
    text-align: center;
    line-height: 14px;
    color: #fff;
    border: 1px solid @color;
    transition: all 0.3 s cubic-bezier(0.645.0.045.0.355.1);
  }
  input[type='checkbox'] {
    display: none;
  }
  input[type='checkbox']:checked + .check-icon {
    background-color: @activeColor;
    border-color: @activeColor;
  }
  &.is-checked-text {
    color: @activeColor;
  }
}
</style>
Copy the code

Results the following

Logical processing

To define the props

<ani-checkbox v-model="checked" @change="onChange"> option </ani-checkbox>Copy the code

We know thatVue3forv-modelIs treated slightly differently than before.As described in the documentpropvalueTurned out to bemodelValue, so we defined within the componentpropsNeed to change

props: {
  modelValue: {
    type: [Boolean, Number, String],
    default: () => undefined
  }
}
Copy the code

We use computed for bidirectional binding

  setup(props, { emit }) {
    const model = computed({
      get() {
        return props.modelValue;
      },
      set(val) {
        emit("update:modelValue", val); }});return {
      model
    }
  }
Copy the code

Add the event callback

<input type="checkbox" v-model="model" @change="onChange"/>
Copy the code

Note here that emit events need to be registered in emits

  emits: ['change'].setup(props, { emit }){...const onChange = () = > {
      emit('change', model.value);
    }

    return {
      onChange
    }
  },
Copy the code

From there, a simple checkbox component is complete. But in the composition-API form we’re using now, we can encapsulate the entire logical useCheckbox function

import { getCurrentInstance } from 'vue';
export function useCheckbox(props) {
  const { proxy } = getCurrentInstance()
  const model = computed({
    get() {
      return props.modelValue;
    },
    set(val) {
      proxy.emit("update:modelValue", val); }});const onChange = () = > {
    proxy.emit('change', model.value);
  }

  return {
    model,
    onChange
  }
}
Copy the code

In the component

import { useCheckbox } from './useCheckbox';

setup(props) {
  return useCheckbox(props);
}
Copy the code

Checkboxes group

We use a lot of UI library components. Checkbox has multiple checkboxes. Most component libraries have multiple checkboxes. Create a new checkbox-group.vue file

<template>
  <div class="ani-checkbox-group">
    <slot></slot>
  </div>
</template>
Copy the code

In practice, we wrap the checkbox component with the checkbox-group

< ANi-checkbox-group V-model ="checkList"> < ANi-checkbox > Option 1 </ani-checkbox> <ani-checkbox> </ani-checkbox-group>Copy the code

Now that we’ve written the template file, let’s refine the logic; Provide/Inject is used to pass parent component parameters

export default {
  name: 'AniCheckboxGroup'.props: {
    modelValue: {
      type: [Array].default: () = > undefined}},emits: ['change'].setup(props, ctx) {
    // Define events
    const changeEvent = value= > {
      ctx.emit('update:modelValue', value);
      nextTick(() = > {
        ctx.emit('change, value); }); }; const modelValue = computed({ get() { return props.modelValue; }, set(val) { changeEvent(val); }}); // Pass provide('CheckboxGroup', { name: 'CheckboxGroup', modelValue, ... toRefs(props), changeEvent }); }}Copy the code

Inject receive in sub-components

// Receives a message from the parent component
export const useCheckboxGroup = () = > {
  // The name here corresponds to the provide name
  const checkboxGroup = inject('CheckboxGroup'{});// Check whether there are multiple box groups
  const isGroup = computed(
    () = > checkboxGroup && checkboxGroup.name === 'CheckboxGroup'
  );

  return {
    isGroup,
    checkboxGroup
  };
};
Copy the code

In useCheckbox, you need to determine whether there are multiple box groups. For specific ideas, see the notes 🤣. In fact, the logic is also simple ~

export const useCheckbox = props= > {
  const { emit } = getCurrentInstance();

  const { isGroup, checkboxGroup } = useCheckboxGroup();

  // If there are multiple select groups, use multiple select group modelValue
  const store = computed(() = >
    checkboxGroup ? checkboxGroup.modelValue.value : props.modelValue
  );

  // Select multiple groups
  const model = computed({
    get() {
      return isGroup.value ? store.value : props.modelValue;
    },
    set(val) {
      if (isGroup.value && Array.isArray(val)) {
        checkboxGroup.changeEvent(val);
      } else {
        emit('update:modelValue', val); }}});// Check whether the multi-check box is selected
  const isChecked = computed(() = > {
    const value = model.value;
    if (isPropType(value, 'boolean')) {
      return value;
    } else if (isPropType(value, 'array')) {
      return value.includes(props.label);
    }
    return null;
  });

  const onChange = e= > {
    const target = e.target;
    const value = target.checked ? true : false;
    emit('change', value, e);
  };

  return {
    model,
    isChecked,
    onChange
  };
};
Copy the code

Here the template needs to be modified, we need to pass in the selected option field value and check whether the checkbox is checked

<label class="checkbox-wrap" > <input type="checkbox" v-model="model" :value="label" :checked="isChecked" @ change = "onChange" / > < I class = "check - icon" > ✓ < / I > < slot > < / slot > < / label >Copy the code

Implementation effect

conclusion

It’s still a matter of wrapping it yourself, and some of the details and ideas in the components can be enlightening to the business logic.