我有一些产品,我想使用复选框根据它们的类别进行筛选.

为此,我有一个父组件,它将可能的类别(例如A、B和C)传递给子组件,并跟踪选中/ Select 的类别.

然而,棘手的部分是,可能的类别是动态的:有时类别数组可能会缩小或增长.

这会导致不正确的行为,因为复选框的布尔值不再对应于正确的位置.例如,前两个复选框处于选中状态,但每当数组更改时,前两个值仍处于选中状态(它们现在是不同的值).这一点如下所示:

Vue Demo

我设法解决了这个问题,让子元素用一个本地属性跟踪复选框本身,并在更新时发送给父母.但是,如果我添加了第二个子项,则子项1和子项2不再同步(例如,当我在移动菜单中使用1个过滤器,而在正常菜单中使用1个过滤器时).如下所示:

Vue Demo

因此,这两种解决方案都还不是最优的,感谢您对这个谜题的任何帮助!

下面是简化的代码.每当最低价格过滤器更改时,FilterOptions数组只显示符合价格条件的可能产品类别.当这种情况发生时,位置不再对应于正确的值.

Child Component - FilterCheckboxItem.vue

<template>

<div
  v-for="(FilterOption, index) in FilterOptions"
> 
  <label for="lname">{{FilterOption}}</label>   
  <input
    type="checkbox"
    :value="FilterOption"
    v-model="modelValue[index]"
  />
</div>


</template>

<script>
export default {
    props: {
     FilterOptions: {
      type: Array,
    },
    modelValue: {
      type: Array,
    },
 },
}
</script>

Parent Component

<template>


  <FilterCheckboxItem
    ref="Category"
    name="Category"
    :FilterOptions="CategoriesWithinPriceReq"
    v-model="FilteredCategories"
  />


<input v-model="MinimalPrice">

</template>

<script>
import FilterCheckboxItem from './Comp.vue'
export default {
  components: { FilterCheckboxItem },
  data(){
    return {
      FilteredCategories: [],
      CategoryOptions: ["A","B","C"],
      MinimalPrice: 5,

      Products: 
        [
        {name: 'test1', price: 10, category: "A"},
        {name: 'test2', price: 15, category: "B"},
        {name: 'test3', price: 20, category: "C"},
        {name: 'test4', price: 8, category: "C"}

        ]  
    }
  },

  computed: {
    FilteredProducts: function(){
    return this.filterProductsByCategory(this.filterProductsByPrice(this.Products))
    },
    CategoriesWithinPriceReq: function(){
      let CategoriesMeetingFilter = this.filterProductsByPrice(this.Products);
      
 
      let uniqueCatgeoriesMeetingFilters = [
        ...new Set(CategoriesMeetingFilter.map(({ category }) => category)),
      ];
      return uniqueCatgeoriesMeetingFilters;
      
    },
  },

  methods: {
    filterProductsByPrice: function(products){
      return products.filter((product)=>product.price>=this.MinimalPrice)
      },
    filterProductsByCategory: function(products){
      const selected_categories = this.CategoriesWithinPriceReq.filter((category, bool) => this.FilteredCategories[bool])
      const results = products.filter((product) => selected_categories.indexOf(product.category) !==-1)
      // Only filter if atleast one box is checked, otherwise return all products
      return selected_categories && selected_categories.length? results: products
      }
  }
}
</script>

推荐答案

每个子元素更新FilteredCategoriesv-model:

  <div>Child 1</div>
  <FilterCheckboxItem
    :FilterOptions="CategoriesWithinPriceReq"
    v-model="FilteredCategories"
  />

    <div>Child 2</div>
  <FilterCheckboxItem
    :FilterOptions="CategoriesWithinPriceReq"
    v-model="FilteredCategories"
  />

当其中一个组件更新变量时,另一个组件必须更新其复选框的选中状态.因此,它必须对即将到来的变化做出react .您可以使用观察者轻松完成此操作:

// FilterCheckboxItem.vue
  setup(props, { emit }) {
    ...
    watch(
      () => props.modelValue, 
      () => checked.value = props.modelValue
    )
    ...
  },

这是updated playground美元


但是,这将导致子组件更新父组件传递的数组(props 中的modelValue变为checked,后者由复选框更新).这被认为是糟糕的状态.由于checked上的监视器,不可能只使用modelValue的副本(即checked.value = [...props.modelValue]),因为它会导致无限循环(设置checked会导致发出,这会更新modelValue,从而再次设置checked).

解决这个问题的方法是通过设置一个@input处理程序,仅在单击复选框时发出.由于你使用的是复选框数组模式v-model,你必须等到数组更新后才能发出:

  <input
    type="checkbox"
    :value="filterOption"
    v-model="checked"
    @input="emitOnNextCycle"
  />

  setup(props, { emit }) {
    const checked = ref([]);
    const emitOnNextCycle = () => setTimeout(() => emit('update:modelValue', checked.value))
    watch(
      () => props.modelValue, 
      () => checked.value = [...props.modelValue]
    )
    return { checked, emitOnNextCycle };
  },

playground

您也可以使用nextTick()Promise.resolve()来代替setTimeout(),在本例中它们的作用都是相同的.


There is a third option, where you don't use a local copy at all, but instead pass on data 和 events directly. This does not work with v-model, you have to use the underlying data binding 和 event, usually :modelValue@update:modelValue, but since you are working with native checkboxes, it is :checked@input 和 you have to write your own array mode:

<!-- in template -->
<input
  type="checkbox"
  :checked="modelValue.includes(filterOption)"
  @input="event => onInput(filterOption, event.target.checked)"
/>

setup(props, { emit }) {
  const onInput = (option, isChecked) => {
    const selection = props.modelValue.filter(selectedOption => selectedOption !== option)
    if (isChecked){
      selection.push(option)
    }
    emit('update:modelValue', selection)
  }
  return {onInput}
}

Now the component does not an internal state anymore, the :checked prop reacts directly to changes to modelValue, 和 @input just emits the update event. This is probably the easiest solution, but it is not always possible.

playground


As a tip, avoid mixing options API elements 和 setup function, it is confusing 和 leads to unclear behavior. Also, the vast majority of JS programmers uses camelCase for variables names, PascalCase is reserved for classes 和 types. Make of that what you will.

希望能有所帮助.

Vue.js相关问答推荐

检测Vue3/Vue-test-utils中的Vuex还原/Mutations

Nuxt 3 I18N浏览器检测不起作用

Vue Datepicker 不会在渲染时显示默认日期

vue3 ssr 不返回纯 html

纵横比元素溢出容器

如何对对象数组进行 v-model

Vue index.html favicon 问题

如何在 Vue 中编译从外部 api 加载的模板

即使响应为 200,也会调用 Axios Catch

如何将 axios/axios 拦截器全局附加到 Nuxt?

为什么不先移动鼠标就无法滚动视差?

单击链接会更改 url,但不会更改页面上的内容/数据

在组件外使用 VueI18n 的问题

为 webpack HMR Vue.js 包含来自外部项目文件夹的可导入模块

[Vue 警告]:无效的props:propsscrollThreshold的类型判断失败.期望的数字,得到字符串

'不提供名为'createRouter'的导出'' vue 3、vite 和 vue-router

来自 Vue.js 的 ESLint 插件的消息中的LHS是什么意思?

Vue路由在新页面上回到顶部

如何删除 vue cli 2?

使用 svelte js 的原因