在 Vue 3 中,如何使自定义组件与复选框组的 v-model 一起工作?

In Vue3, how to make Custom Component work with v-model for group of checkboxs?

下面给出的代码您可以看到 v-model 的魔力,在选中/取消选中复选框后,数组 checkedNames 将自动 add/remove 命名。我们不必向数组中的 push/slice/filter 名称写入任何内容,对吗?

const { ref } = Vue;

const App = {
  setup () {
    const checkedNames = ref([])
    return { checkedNames }
  }
}

Vue.createApp(App).mount("#app");
<script src="https://unpkg.com/vue@3.0.11/dist/vue.global.js"></script>

<div id="app">
  <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
  <label for="jack">Jack</label>
  <input type="checkbox" id="john" value="John" v-model="checkedNames">
  <label for="john">John</label>
  <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
  <label for="mike">Mike</label>
  <br>
  <span>Checked names: {{ checkedNames }}</span>
</div>

我的问题是:如何使用自定义组件实现相同的功能?

你可以看到,在下面给出的代码片段中,我尝试了但我不喜欢它,因为我们缺少 v-model 的魔力,相反我在我的函数 addOrRemoveItem()[=27 中处理它=]

heads-up: It may seem to you that I am over explaining :) sorry for that.

我关注了this tutorial for some idea but was not much helpful, then referred vue official doc here。下面是功能代码,但有点矫枉过正。

const { ref, createApp } = Vue;

const app = createApp({
  setup() {
    const itemsSelected = ref([]);
    const items = ref([
      { id: '1', name: 'Jack' }, 
      { id: '2', name: 'John' }, 
      { id: '3', name: 'Mike' }, 
    ]);

    const addOrRemoveItem = (itemId) => {
      const exists = itemsSelected.value.includes(itemId);

      if (exists) {
        itemsSelected.value = itemsSelected.value.filter((id) => id !== itemId);
      } else {
        itemsSelected.value.push(itemId);
      }
    };

    return { 
      items,
      itemsSelected,
      addOrRemoveItem,
    };
  },
});

app.component('custom-checkbox', {
   props: {
    item: { type: Object, default: () => ({}) },
    modelValue: { type: Array, default: () => [] },
  },
  template: `
    <div>
      <input
        type="checkbox" :value="item.id"
        @change="$emit('update:model-value', $event.target.checked)"
      > {{ item.name }}
    </div>
    `
})

app.mount("#app");
<script src="https://unpkg.com/vue@3.0.11/dist/vue.global.js"></script>

<div id="app">
  <div><code>itemsSelected: {{ itemsSelected }}</code></div>
 <hr />
  <custom-checkbox
    v-for="item in items"
    :key="item.id"
    :item="item"
    :model-value="itemsSelected"
    @update:model-value="addOrRemoveItem(item.id)"
  ></custom-checkbox>
</div>

如上所述,当我按照 doc $emit('update:model-value', ...) 中提到的代码时,代码有点矫枉过正,它可以是任何东西,例如:$emit('val-updated'),这是之后的简化版本删除不需要的 prop 并减少 $emit.

的长度

const { ref, createApp } = Vue;

const app = createApp({
  setup() {
    const itemsSelected = ref([]);
    const items = ref([
      { id: '1', name: 'Jack' }, 
      { id: '2', name: 'John' }, 
      { id: '3', name: 'Mike' }, 
    ]);

    const addOrRemoveItem = (itemId) => {
      const exists = itemsSelected.value.includes(itemId);

      if (exists) {
        itemsSelected.value = itemsSelected.value.filter((id) => id !== itemId);
      } else {
        itemsSelected.value.push(itemId);
      }
    };

    return { 
      items,
      itemsSelected,
      addOrRemoveItem,
    };
  },
});

app.component('custom-checkbox', {
   props: {
    item: { type: Object, default: () => ({}) },
  },
  template: `
    <div>
      <input
        type="checkbox" :value="item.id"
        @change="$emit('val-updated')"
      > {{ item.name }}
    </div>
    `
})

app.mount("#app");
<script src="https://unpkg.com/vue@3.0.11/dist/vue.global.js"></script>

<div id="app">
  <div><code>itemsSelected: {{ itemsSelected }}</code></div>
 <hr />
  <custom-checkbox
    v-for="item in items"
    :key="item.id"
    :item="item"
    @val-updated="addOrRemoveItem(item.id)"
  ></custom-checkbox>
</div>

根本不需要addOrRemoveItem()方法。只需让 checkbox 的默认 Vue v-model 逻辑来完成繁重的工作。您唯一需要做的就是为 v-model 使用 computed 道具(因为无法直接使用道具,因为不能从子组件中更改道具)

const {
  ref,
  createApp
} = Vue;

const app = createApp({
  setup() {
    const itemsSelected = ref([]);
    const items = ref([{
        id: '1',
        name: 'Jack'
      },
      {
        id: '2',
        name: 'John'
      },
      {
        id: '3',
        name: 'Mike'
      },
    ]);

    return {
      items,
      itemsSelected,
    };
  },
});

app.component('custom-checkbox', {
  props: {
    item: {
      type: Object,
      default: () => ({})
    },
    modelValue: {
      type: Array,
      required: true
    }
  },
  emits: ['update:modelValue'],
  computed: {
    model: {
      get() {
        return this.modelValue
      },
      set(value) {
        this.$emit('update:modelValue', value)
      }
    }
  },
  template: `
    <div>
      <input
        type="checkbox" :value="item.id" v-model="model"
      > {{ item.name }}
    </div>
    `
})

app.mount("#app");
<script src="https://unpkg.com/vue@3.0.11/dist/vue.global.js"></script>

<div id="app">
  <div><code>itemsSelected: {{ itemsSelected }}</code></div>
  <hr />
  <custom-checkbox v-for="item in items" :key="item.id" :item="item" v-model="itemsSelected"></custom-checkbox>
</div>