Home  >  Q&A  >  body text

vue 3 composition api component with checkbox array and toggle all

<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>
P粉670838735P粉670838735415 days ago596

reply all(1)I'll reply

  • P粉203792468

    P粉2037924682023-08-31 15:28:23

    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 call emit().
    toggleAll will ultimately create a function that works with internalModel.
    Here is an example Vue SFC Playground. < /p>


    HomeView.vue:

    <script setup lang="ts">
    import { ref } from 'vue';
    import AppList, { type Item } from './AppList.vue';
    
    const fooItems = ref<Item[]>([
      { id: 1, name: 'foo 1', checked: false },
      { id: 2, name: 'foo 2', checked: false },
      { id: 3, name: 'foo 3', checked: false },
      { id: 4, name: 'foo 4', checked: false },
      { id: 5, name: 'foo 5', checked: true },
    ]);
    const fooSelected = ref<number[]>([]);
    fooItems.value.map(item => item.checked && fooSelected.value.push(item.id))
    </script>
    
    <template>
      <AppList :items="fooItems" v-model:selected="fooSelected"></AppList>
      <div>fooselected: {{ fooSelected }}</div>
    </template>

    AppList.view:

    <script setup lang="ts">
    import { ref } from 'vue';
    
    export interface Item {
      id: number;
      name: string;
      checked: boolean
    }
    
    const props = defineProps<{
      items: Item[];
      selected: number[]
    }>();
    
    const emit = defineEmits(['update:selected']);
    
    const internalModel = ref(props.selected);
      
    const selectedHandler = () => emit('update:selected', internalModel.value);
    
    const toggleAll = ($event) => {
      internalModel.value = [];
      if ( ($event.target as HTMLInputElement).checked ) {
        props.items.map(item => internalModel.value.push(item.id));
      }
      emit('update:selected', internalModel.value);
    };
    </script>
    
    <template>
      <label>
        <input type="checkbox" @change="toggleAll($event)" :checked="internalModel.length === items.length" />
        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(item.id)" :checked="item.checked"/>
            <span>{{ item.name }}</span>
          </label>
        </li>
      </ul>
      internalModel: {{ internalModel }}
    </template>

    reply
    0
  • Cancelreply