You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3执行函数后DOM不更新问题求助(点赞收藏功能)

Vue3 点赞/收藏功能UI不即时更新问题

我在Vue3中开发点赞/收藏功能时遇到问题:点击点赞或取消点赞后,前端UI不会即时更新,只有刷新页面才能显示最新状态。后端的点赞、取消点赞功能运行正常,以下是父组件和子组件代码,求解决DOM重新渲染的问题。


父组件代码

<template>
  <Suspense>
    <template #default>
      <div class="is-flex is-1 is-flex-wrap-wrap" v-if="reciepes">
        <ReciepeCard
          class="card radius-small mx-2 my-3 p-3"
          v-for="(reciepe, i) in reciepes"
          :reciepe="reciepe"
          :key="i"
          :savedDishesId="savedReciepesIds"
          @openReciepe="openFunc(reciepe)"
          @likeIndi="isLiked(reciepe)"
        >
        </ReciepeCard>
      </div>
    </template>
  </Suspense>
</template>

<script setup>
import ReciepeCard from '../components/reciepe-cards/reciepeCard.vue'

const reciepeStore = useReciepeStore(),
  userStore = useUserStore(),
  loginStore = useLoginStore(),
  router = useRouter()
const reciepes = ref([]),
  username = ref('User'),
  savedReciepesIds = ref([])

onMounted(async () => {
  fetchData()
  let userId = VueCookies.get('id')
  if (userId) {
    let user = await userStore.fetchAccDetails(VueCookies.get('id'))
    username.value = user.username
  }
  savedReciepesIds.value = await userStore.fetchSavedDishesId(userId)
  console.log(savedReciepesIds.value)
})

async function isLiked(userId) {
  console.log("parent");
  savedReciepesIds.value = await userStore.fetchSavedDishesId(userId)
}
async function fetchData() {
  reciepes.value = await reciepeStore.getReciepes()
}
console.log(loginStore.isAuth())

function openFunc(reciepe) {
  router.push({ path: `/reciepe/${reciepe._id}` })
}
</script>

子组件代码

<template>
  <div class="reciepe-card bg-color-white" style="cursor: pointer">
    <div class="card-head" @click="emits('openReciepe')">
      <figure class="">
        <img :src="props.reciepe.img" alt="Recipe Image" class="image radius-default" />
      </figure>
    </div>
    <div class="card-desc" @click="emits('openReciepe')">
      <h3 class="is-size-3 color-tx-sec">{{ props.reciepe.dish_name }}</h3>
      <p class="is-size-5">{{ resizeText(props.reciepe.description) + '...' }}</p>
    </div>
    <div class="is-flex is-justify-content-space-between is-align-items-center is- mt-3">
      <p class="has-text-link" @click="emits('openReciepe')">View more</p>
      <Icon
        icon="ph:heart-bold"
        v-if="!isLiked"
        style="height: 2rem; width: 2rem"
        @click="toogleLike()"
      ></Icon>
      <Icon
        icon="mdi:heart"
        color="red"
        v-else
        style="height: 2rem; width: 2rem"
        @click="toogleLike()"
      ></Icon>
    </div>
  </div>
</template>

<script setup>
import { Icon } from '@iconify/vue'
import { computed, onMounted, ref, watch } from 'vue'
import { useUserStore } from '../../stores/userStore'

import VueCookies from 'vue-cookies'

const isLiked = ref(false)
onMounted(() => {
  updateLikeStats()
})

watch(
  () => props.savedDishesId,
  () => {
    updateLikeStats()
  }
)
// const isLiked = computed(() => {
//   if (props.savedDishesId != null || props.savedDishesId != undefined)
//     return !props.savedDishesId.includes(props.reciepe._id)
//   else return false
// })
const userStore = useUserStore(),
  userId = ref(VueCookies.get('id') || null)
const emits = defineEmits(['openReciepe', 'likeIndi'])
const props = defineProps({
  reciepe: {
    type: Object,
    required: true
  },
  savedDishesId: {
    type: Array,
    required: true,
    default: () => []
  }
})

function resizeText(text) {
  return text.slice(0, 120)
}

async function toogleLike() {
  await userStore.toogleLikeReciepe(props.reciepe._id, { userId: userId.value })
  emits('likeIndi',userId.value);
  updateLikeStats()
}

function updateLikeStats() {
  isLiked.value = props.savedDishesId.includes(props.reciepe._id)
  console.log(isLiked.value)
}
</script>

问题修复方案

1. 修正v-for的key值(父组件)

当前用数组索引i作为key,会导致Vue无法正确识别组件身份,改为用唯一的reciepe._id:

<ReciepeCard
  class="card radius-small mx-2 my-3 p-3"
  v-for="reciepe in reciepes"
  :reciepe="reciepe"
  :key="reciepe._id" <!-- 替换为唯一ID -->
  :savedDishesId="savedReciepesIds"
  @openReciepe="openFunc(reciepe)"
  @likeIndi="isLiked" <!-- 直接传函数即可,子组件emit的参数会自动接收 -->
></ReciepeCard>

2. 改用computed自动计算点赞状态(子组件)

去掉手动维护的isLiked ref和更新函数,用computed自动响应props变化:

// 删除以下代码:
// const isLiked = ref(false)
// onMounted(() => { updateLikeStats() })
// watch(() => props.savedDishesId, () => { updateLikeStats() })
// function updateLikeStats() { ... }

// 替换为:
const isLiked = computed(() => {
  return props.savedDishesId.includes(props.reciepe._id)
})

这样只要父组件的savedReciepesIds更新,子组件的isLiked会自动同步,UI即时刷新。

3. 可选:优化数组监听(若坚持用ref)

如果不想用computed,需要给watch添加深度监听,因为数组是引用类型,浅监听无法检测内部元素变化:

watch(
  () => props.savedDishesId,
  () => {
    updateLikeStats()
  },
  { deep: true } // 添加深度监听配置
)

内容的提问来源于stack exchange,提问作者Valor Hec07

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 17:53:17