Vue.js 当我使用计算方法过滤组件时,挂载方法没有触发

Vue.js Mounted method is not triggering when I use computed method for filtering components

我需要显示从外部获得的图像 api。 我有 ProductCard.vue,我在 mounted 挂钩中获取图像。

<template>
  <q-card
    class="my-card column text-center no-border-radius"
    flat
    bordered
  >
    <q-img
      :src="PRODUCT_IMG_SRC"
      sizes="(max-width: 400px) 400w,
              (min-width: 400px) and (max-width: 800px) 800w,
              (min-width: 800px) and (max-width: 1200px) 1200w,
              (min-width: 1200px) 1600w"
      loading="eager"
    />
    <q-card-section>
      <div class="text-caption text-grey text-left">
        {{ truncate(product.description, 100) }}
      </div>
    </q-card-section>
  </q-card>
</template>

<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue';
import { truncate } from 'src/utils/textUtils';
import fallback_image from 'src/assets/dluhopis_houses_responsive.svg';

export default defineComponent({
  name: 'ProductCard',
  props: {
    product: Object,
    chosen: Boolean,
  },
  setup(props) {
    const PRODUCT_IMG_SRC = ref();

    onMounted(async() => {
      const IMG_URL = `https://ydc.datanasiti.cz/api/product/${props?.product?.externalId}/image`;
      const response = await fetch(IMG_URL);


      if (response.status === 200) {
        return (PRODUCT_IMG_SRC.value = IMG_URL);
      } else {
        PRODUCT_IMG_SRC.value = fallback_image;
      }
    });

    return {
      truncate,
      PRODUCT_IMG_SRC,
    };
  },
});
</script>

我在 v-for 循环中将此组件加载到父组件中,我循环计算 属性 PageProducts 以过滤每页的产品。


<template>
  <div
    v-if="pageProducts"
    :class="[productChosen ? '' : 'row items-stretch q-col-gutter-x-md']"
    class="q-pb-md wrap"
  >
    <template v-if="!productChosen">
      <div
        v-for="(product, index) in pageProducts"
        :key="`md-${index}`"
        class="col-sm-5 col-md-4 col-lg-3 col-xl-2 q-mt-md justify-sm-center flex"
      >
        <product-card
          :product="product"
          @product-detail="toggleDetail"
          @choose-product="chooseProduct"
        />
      </div>
    </template>
    <div
      class="q-pa-lg flex flex-center col-12"
    >
      <q-pagination
        v-if="!productChosen"
        v-model="currentPage"
        :max="pagesCount()"
        direction-links
      />
    </div>
   
  </div>
</template>

<script lang="ts">
import { defineComponent, onMounted, reactive, ref, toRefs, computed } from 'vue';
import ProductCard from 'src/components/ProductCard.vue';
import { products } from 'src/api/api';
import { AxiosResponse } from 'axios';
import useDate from 'src/utils/useDate';
import { useQuasar } from 'quasar';
import { IProduct, PAYOUT_FREQUENCY } from 'src/interfaces/Purchase';



export default defineComponent({
  name: 'ProductCatalog',
  components: {
    ProductCard,
  },
  emits: ['chosen'],
  // eslint-disable-next-line no-unused-vars
  setup(props, { emit }) {
    const $q = useQuasar();
    const productsRef = ref([]);
    const currentPage = ref(1);
    const searchProduct = ref('');
    const productChosen = ref();
    const PAGE_SIZE = 8;
    const { formatDateDDMYYYY, yearsDiff } = useDate();
    const dialogState = reactive({
      expanded: false,
      product: {} as IProduct
    });


    onMounted(() => {
      products.getProducts().then((response: AxiosResponse) => {
        productsRef.value = response.data[0];
      });
    });

    const toggleDetail = (product: any) => {
      dialogState.product = product;
      dialogState.expanded = true;
    };

    const chooseProduct = (product: IProduct, isChosen: boolean) => {
      productChosen.value = product;
      if(product) {
        $q.notify({
          message: 'Produkt byl úspěšně vybrán.',
          icon: 'check_circle',
          color: 'positive',
        });
      }
      emit('chosen', isChosen, product);
      dialogState.expanded = false;
    };


    const pagesCount = (() => {
      if (productsRef.value.length > PAGE_SIZE) {
        return Math.ceil(productsRef.value.length / PAGE_SIZE);
      }
      return 1;
    });

    const pageProducts = computed(() => {
      const pageProducts = productsRef.value;
      if (pagesCount() > 1) {
        pageProducts = productsRef.value.slice(
          (currentPage.value * PAGE_SIZE) - PAGE_SIZE,
          currentPage.value * PAGE_SIZE
        );
      }
      if (searchProduct.value) {
        return productsRef.value.filter((product: any) => {
          return searchProduct.value
            .toLowerCase()
            .split(' ')
            .every((v: any) => product.name.toLowerCase().includes(v));
        });
      } else {
        return pageProducts;
      }
    });

    /* eslint-disable-next-line vue/script-indent */
    const payoutFrequency = computed(() => {
      const frequency = {
        1: PAYOUT_FREQUENCY.MONTHLY,
        3: PAYOUT_FREQUENCY.QUATERLY,
        6: PAYOUT_FREQUENCY.SEMI_ANNUAL,
        12: PAYOUT_FREQUENCY.ANNUAL
      } as any;

      return frequency[dialogState.product.frequency];
    });

    return {
      productsRef,
      pageProducts,
      pagesCount,
      searchProduct,
      currentPage,
      payoutFrequency,
      productChosen,
      chooseProduct,
      toggleDetail,
      formatDateDDMYYYY,
      yearsDiff,
      ...toRefs(dialogState)
    };
  },
});
</script>

问题是在子组件中 ProductCard.vue 当父组件中的计算属性发生变化时,不会为每个产品调用安装方法。它仅针对前 8 个产品调用,然后当我从第 7 页切换到第 4 页时调用。很奇怪。

任何提示为什么会这样?或者您知道如何使它变得更好的方法吗?

当 Vue 挂载一个组件时,它只会作为 component lifecycle 的一部分发生一次。之后,Vue 的反应性系统会随着反应性数据的变化而简单地更新 DOM 。所以很正确,你的 #mounted 钩子只触发一次。

跟进我之前的 post,如果您在迭代器 (v-for) 中传递给模板的产品实际上正在发生变化(例如由于分页)并且您需要 re-render 整个组件,你需要 key the component 通知 Vue 你的响应式道具已经改变,组件应该 re-render 从头开始​​编辑。

<product-card
  :product="product"
  @product-detail="toggleDetail"
  @choose-product="chooseProduct"
  :key="product.id"
/>