vue 3 composition api component with checkbox array and toggle all
P粉670838735
2023-08-30 20:55:11
<p>We have decided to gradually move from knockout to the vue 3 composition api using typescript, and I'm trying to understand the anti-pattern of mutating props. I have a working component that is doing its intended job, but basically I want to confirm that the way it is written is the recommended approach. </p>
<p>A fairly simple example is a checkbox list component with a toggle on it: </p>
<p>My biggest question is if what I'm doing in AppList.vue is correct, I'm doing <code>const internalModel = toRef(props.selected ?? []);</code> to get the unavailable Variables in components and <code>selectedHandler</code> - events and <code>toggleAll</code> - evaluate to emit OUT but here I manually keep <code>selected</code> and <Code>Internal Model</Code> Synchronization. Using two variables for the same thing feels cumbersome, but at the same time it does make sense since the internal model doesn't need to interfere with the view. </p>
<p>I know there is an example on vuejs.org where you can use an array on <code>v-model</code> to represent multiple checkboxes, but it's not inside a component or as a prop, so it Not exactly the same, this feels more complicated. I spent most of the day trying to get it right but there aren't that many vue 3 search results and I didn't find any results at all for this particular problem. </p>
<p>HomeView.vue:</p>
<p>
<pre class="brush:html;toolbar:false;"><script set lang="ts">
import { ref } from 'vue';
import AppList, { type Item } from '@/components/AppList.vue';
const fooItems = ref<Item[]>([
{ id: 1, name: 'foo 1' },
{ id: 2, name: 'foo 2' },
{ id: 3, name: 'foo 3' },
{ id: 4, name: 'foo 4' },
{ id: 5, name: 'foo 5' },
]);
const fooSelected = ref<number[]>([]);
</script>
<template>
<AppList :items="fooItems" v-model:selected="fooSelected"></AppList>
<div>fooselected: {{ fooSelected }}</div>
</template></pre>
</p>
<p>组件/Applist.vue:</p>
<p>
<pre class="brush:html;toolbar:false;"><script setup lang="ts">
import { computed, toRef } from 'vue';
export interface Item {
id: number;
name: string;
}
const props = defineProps<{
items: Item[];
selected?: number[];
}>();
const internalModel = toRef(props.selected ?? []);
const emit = defineEmits<{
'update:selected': [selected: number[]];
}>();
const selectedHandler = (e: Event) => {
const target = <HTMLInputElement>e.target;
if (props.selected && target) {
if (target.checked) {
emit('update:selected', [...props.selected, Number(target.value)]);
} else {
emit(
'update:selected',
props.selected.filter((i: number) => i !== Number(target.value))
);
}
}
};
const toggleAll = computed({
get: () => internalModel.value.length === props.items.length && internalModel.value.every((s) => props.items.map((item) => item.id).includes(s)),
set: (value) => {
if (value) {
emit(
'update:selected',
props.items.map((i) => i.id)
);
internalModel.value = props.items.map((i) => i.id);
} else {
emit('update:selected', []);
internalModel.value = [];
}
},
});
</script>
<template>
<label>
<input type="checkbox" v-model="toggleAll" />
toggle all
</label>
<ul>
<li v-for="item in items" :key="item.id">
<label>
<input type="checkbox" :value="item.id" v-model="internalModel" @change="selectedHandler" />
<span>id {{ item.name }}</span>
</label>
</li>
</ul>
internalModel: {{ internalModel }}
</template></pre>
</p>
It seems to me that this can be done in some simpler way.
fooItems
There should probably be an initial state of "checked".In
selectedHandler
, just callemit()
.toggleAll
will ultimately create a function that works withinternalModel
.Here is an example Vue SFC Playground.
HomeView.vue:
AppList.view: