在 Vue 3 中创建复选框组件时实现组的正确方法是什么?

What is the proper way to implement groups when creating a checkbox component in Vue 3?

我正在 Vue 3 中创建一个复选框组件,它使用 v-model 为复选框组创建到数组的绑定。此组绑定是一项可选功能,在大多数情况下,复选框的值不会填充数组,而是 null 或某个字符串。我想我离解决方案越来越近了,但它似乎相当复杂,尤其是考虑到您可以像这样非常简单地绑定 3 个输入:https://medium.com/dataseries/vue-3-v-model-c896566aed64

这是我在 App.vue

中的实现
<template>
  <checkbox modelValue="apple" v-model:group="group" />
  <checkbox modelValue="orange" v-model:group="group" />
  <checkbox modelValue="grape" v-model:group="group" />
  <p>{{ group }}</p>
</template>

<script setup>
  import { ref } from 'vue';
  import Checkbox from './components/Checkbox.vue'
  const group = ref(["apple"]); //"Apple" should start out checked, but it currently doesn't.
</script>

这里是Checkbox.vue

<template>
  <input 
  type="checkbox"
  :value="modelValue"
  @change="updateValue($event)"
  />
  <label for="checkbox">{{ modelValue }}</label>
</template>

<script setup>
  import { defineEmits, ref, onMounted } from 'vue';
  const checkbox = ref(null);
  const props = defineProps({
    modelValue: String,
    group: Array
  })

  const emit = defineEmits(['update:modelValue'])

  onMounted(()=>{
    //Some code to determine initial state depending on what props.group looks like?
  })
  function updateValue(e) {
    if (e.target.checked){
      props.group.push(e.target.value);
    }
    if(!e.target.checked){
      const toRemove = props.group.indexOf(e.target.value);
      props.group.splice(toRemove, 1);
    }
    emit('update:modelValue', props.group)
  }
  defineExpose({ checkbox})
</script>

目前,数组根据选中和未选中的内容填充和取消填充,但最初设置为选中的“apple”目前不是。令我困扰的是,输入的实现可以如此简单,但我发现自己编写了一大堆代码来保持复选框组和 group 数组之间的双向绑定。在我深入 onMounted 钩子中编写一大堆代码之前,我想知道是否有更简单的方法来完成所有这些工作?甚至我为 updateValue() 编写的代码似乎有点太多了。

并不是说实现 pr 路径是错误的,但我倾向于使用 v-modelwatch 而不是事件侦听器。我相信这使得处理重复值之类的事情变得更简单。

App.vue

<template>
  <checkbox value="apple" v-model="group" />
  <checkbox value="orange" v-model="group" />
  <checkbox value="grape" v-model="group" />
  <hr/><!-- duplicates -->
  <checkbox value="apple" v-model="group" />
  <checkbox value="grape" v-model="group" />
  <p>{{ group }}</p>
</template>

<script setup>
  import { ref } from 'vue';
  import Checkbox from './Checkbox.vue'
  const group = ref(["apple"]);
</script>

Checkbox.vue

<template>
  <label>
    <input 
      type="checkbox"
      :value="value"
      v-model="checked"
    />
    {{ value }}
  </label>
</template>

<script setup>
  import { watch, ref } from 'vue';
  const checkbox = ref(null);
  const props = defineProps({
    modelValue: Array,
    value: String
  });
  
  const checked = ref(props.modelValue.includes(props.value));
  
  watch(checked, (v) => {
    if(!v && props.modelValue.includes(props.value)) {
      const toRemove = props.modelValue.indexOf(props.value);
      props.modelValue.splice(toRemove, 1);
    } else if(v && !props.modelValue.includes(props.value)) {
      props.modelValue.push(props.value);
    }
  })
  
  watch(props.modelValue, (mv) => {
    if(checked.value && !mv.includes(props.value)) {
      checked.value = false;
    } else if(!checked.value && mv.includes(props.value)) {
      checked.value = true;
    }
  })
</script>

在示例中,我使用了 ref 来存储内部 true/false 值。然后有两个手表处理 checkedmodelValue 之间的差异(映射到组数组)。

link to SFC PLayground