Vue 3 组合 API 对象数据在应用过滤器后未更新

Vue 3 composition API object data not updating after apply the filter

我有一个产品列表的反应对象,我在我的模板中显示产品。现在我制作了一个过滤器,它通过以下方式过滤产品对象 对象的产品类型键。问题是当我应用过滤器时,它在控制台中显示正确的值,但在模板中,它不反映任何更改。在 vue 2 中,我使用了 this.$forceUpdate();,它似乎对我有用。在 vue3 组合中做这件事的正确方法是什么 API?

我做错了什么,但不知道该怎么做。谢谢你在 advacne.

这里附上我的代码沙盒link Code sandbox

<template>
  <div>
    <div
      class="assets-dropdwon accordian"
      v-for="(pata, index) in productData"
      :key="index"
    >
      {{ pata["name"] }}
    </div>

    <button @click="filterProductData()">FILTER</button>
  </div>
</template>

<script>
import { reactive } from "vue";
export default {
  name: "App",
  setup() {
    const productTypeVal = 1;
    let productData = reactive({
      1: {
        sort: 1,
        name: "Product 1",
        product_type: 1,
      },
      2: {
        sort: -1,
        name: "Product 2",
        product_type: 2,
      },
      3: {
        sort: 0,
        name: "Product 3",
        product_type: 1,
      },
      4: {
        sort: 5,
        name: "Product 4",
        product_type: 3,
      },
    });
    let productInitData = {
      1: {
        sort: 1,
        name: "Product 1",
        product_type: 1,
      },
      2: {
        sort: -1,
        name: "Product 2",
        product_type: 2,
      },
      3: {
        sort: 0,
        name: "Product 3",
        product_type: 1,
      },
      4: {
        sort: 5,
        name: "Product 4",
        product_type: 3,
      },
    };

    let filteredProducts = reactive({});

    const filterProductData = () => {
      Object.keys(productInitData).map((key) => {
        if (productInitData[key]["product_type"] === productTypeVal) {
          filteredProducts[key] = productInitData[key];
        }
      });
      productData = filteredProducts;
      console.log(productData);
    };

    return {
      productData,
      productTypeVal,
      filterProductData,
    };
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

请看Vue.js Documentation: Limitations of reactive():

The reactive() API has two limitations:

  1. It only works for object types (objects, arrays, and collection types such as Map and Set). It cannot hold primitive types such as string, number or boolean.
  2. Since Vue's reactivity tracking works over property access, we must always keep the same reference to the reactive object. This means we can't easily "replace" a reactive object because the reactivity connection to the first reference is lost.

后一点是你的代码的问题:你没有改变这个对象的属性的状态,这是允许的,而是改变了变量引用的引用,我认为这个引用的重新分配是是什么导致反应性丧失。

因此,我认为您可以使用几种可能的解决方案,包括:

  1. 正在更改 productData 的实际对象属性。为此,您可能需要删除所有属性并从 productInitData 中删除它们 re-add 如果它们符合条件:
function myFilter() {
  // remove all properties from productData
  Object.keys(productData).forEach((key) => delete productData[key]);

  // re-add properties that we want to display
  Object.keys(productInitData).forEach((key) => {
    if (productInitData[key]["product_type"] === productTypeVal) {
      productData[key] = productInitData[key];
    }
  });
}
  1. 使用并显示计算的 属性,其中 v-for 在 computedProducts 字段上。这可能需要其他字段,在我的示例中,我使用布尔字段 isFiltered:
let isFiltered = reactive({ value: false });

function toggleFiltered() {
  isFiltered.value = !isFiltered.value;
}

const computedProducts = computed(() => {
  if (isFiltered.value) {
    let myFilteredProducts = {};
    Object.keys(productInitData).map((key) => {
      if (productInitData[key]["product_type"] === productTypeVal) {
        myFilteredProducts[key] = productInitData[key];
      }
    });
    return myFilteredProducts;
  } else {
    return productInitData;
  }
});

并在模板中:

<div
  class="assets-dropdwon accordian"
  v-for="(pata, index) in computedProducts"
  :key="index"
>
  {{ pata["name"] }}
</div>

<button @click="toggleFiltered()">FILTER 1</button>
  1. 将反应对象包装在另一个对象中,以便可以更改内部对象的引用并保持反应。但是,如果我们要走这么远,不妨使用 Pinia 或 Vuex 等商店。

例如:

<template>
  <div>
    <div
      class="assets-dropdwon accordian"
      v-for="(pata, index) in computedProducts"
      :key="index"
    >
      {{ pata["name"] }}
    </div>

    <button @click="toggleFiltered()">FILTER 1</button>

    <br />
    <br />
    <div
      class="assets-dropdwon accordian"
      v-for="(pata, index) in productData"
      :key="index"
    >
      {{ pata["name"] }}
    </div>

    <button @click="myFilter">FILTER 2</button>
  </div>
</template>

<script>
import { computed, reactive } from "vue";
export default {
  name: "App",
  setup() {
    const productTypeVal = 1;
    let productData = reactive({
      1: {
        sort: 1,
        name: "Product 1",
        product_type: 1,
      },
      2: {
        sort: -1,
        name: "Product 2",
        product_type: 2,
      },
      3: {
        sort: 0,
        name: "Product 3",
        product_type: 1,
      },
      4: {
        sort: 5,
        name: "Product 4",
        product_type: 3,
      },
    });
    let productInitData = {
      1: {
        sort: 1,
        name: "Product 1",
        product_type: 1,
      },
      2: {
        sort: -1,
        name: "Product 2",
        product_type: 2,
      },
      3: {
        sort: 0,
        name: "Product 3",
        product_type: 1,
      },
      4: {
        sort: 5,
        name: "Product 4",
        product_type: 3,
      },
    };

    let isFiltered = reactive({ value: false });

    function toggleFiltered() {
      isFiltered.value = !isFiltered.value;
    }

    const computedProducts = computed(() => {
      if (isFiltered.value) {
        let myFilteredProducts = {};
        Object.keys(productInitData).map((key) => {
          if (productInitData[key]["product_type"] === productTypeVal) {
            myFilteredProducts[key] = productInitData[key];
          }
        });
        return myFilteredProducts;
      } else {
        return productInitData;
      }
    });

    function myFilter() {
      // remove all properties from productData
      Object.keys(productData).forEach((key) => delete productData[key]);

      // re-add properties that we want to display
      Object.keys(productInitData).forEach((key) => {
        if (productInitData[key]["product_type"] === productTypeVal) {
          productData[key] = productInitData[key];
        }
      });
    }

    return {
      productData,
      productTypeVal,
      computedProducts,
      toggleFiltered,
      myFilter,
    };
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>