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"
/>
我需要显示从外部获得的图像 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"
/>