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

Vue JS + Quasar组件热更新前无法获取props问题求助

问题根源

你的问题出在两个关键环节:

  1. 页面组件在onMounted生命周期里才从路由参数赋值给响应式变量,此时SurveyEdit已经完成初始化,拿到的props是初始空值
  2. SurveyEdit内部仅在setup执行时给本地响应式变量赋值一次,没有监听props的后续更新,所以页面组件更新数据后,子组件的表单内容不会同步更新
  3. 热更新时页面组件的响应式变量已经有值,SurveyEdit初始化就能拿到正确props,因此显示正常

修复方案

1. 页面组件:提前处理路由参数(或监听参数变化)

不需要等到onMounted,直接在setup中处理路由参数,同时可选监听参数变化以支持路由动态更新:

<template>
  <q-page class="bg-grey-3 column items-center">
    <SurveyEdit
      :question="question"
      :correctAnswer="correctAnswer"
      :choices="choices"
      :imageURL="imageURL"
    />
  </q-page>
</template>

<script>
import SurveyEdit from 'src/components/SurveyEdit.vue'
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'

export default {
  name: 'EditPage',
  components: {
    SurveyEdit
  },
  setup() {
    const route = useRoute()
    const question = ref('')
    const choices = ref([])
    const correctAnswer = ref(null)
    const imageURL = ref('')

    // 初始化处理路由参数
    const updateFromQuery = (query) => {
      imageURL.value = query.imageURL || ''
      question.value = query.question || ''
      choices.value = query.choices ? query.choices.split('|') : []
      correctAnswer.value = query.correctAnswer ? Number(query.correctAnswer) : null
    }

    // 首次执行+监听路由参数变化
    watch(() => route.query, updateFromQuery, { immediate: true })

    return {
      question,
      choices,
      correctAnswer,
      imageURL,
    }
  }
}
</script>

2. SurveyEdit组件:监听props变化同步本地数据

因为表单需要双向绑定,不能直接修改props,所以要监听props变化,同步到本地响应式变量:

<template>
  <q-card class="survey-card" flat bordered>
    <img v-if="imageURL" :src="imageURL">
    <q-form
      @submit="onSubmit"
      class="q-gutter-md"
    >
      <q-file clearable color="orange" standout bottom-slots
        label="Загрузить изображение для опроса"
        @update:model-value="onImageLoaded"
        v-model="loaderModel"
        class="image-loader"
      >
        <template v-slot:prepend>
          <q-icon name="attach_file" />
        </template>
        <template v-slot:hint>
          Для удаления изображения нажмите на крестик в правой части загрузчика
        </template>
      </q-file>
      <q-input
        filled
        v-model="question"
        label="Заголовок опроса *"
        hint=""
        lazy-rules
        :rules="[ val => val?.length > 0 || 'Пожалуйста, введите что-нибудь']"
      />
      
      <ul class="choices-list bg-grey-2">
        <q-input 
          v-for="(choice, index) in choices"
          :key="index"
          filled
          label="Ответ *"
          v-model="choices[index]"
          hint=""
          lazy-rules
          :rules="[ val => val?.length > 0 || 'Пожалуйста, введите что-нибудь']"
          class="choice"
        >
          <template v-slot:append>
            <q-btn round dense flat icon="close" @click="onDeleteChoice(index)"/>
          </template>
          <template v-slot:after>
            <q-btn :class="{'bg-green-3': index==correctAnswerIndex}" round dense flat icon="check" @click="onSelectCorrectAnswer(index)"/>
          </template>
        </q-input>
      </ul>
      <div class="column items-center">
        <q-btn label="Добавить ответ" color="secondary" class="center" @click="onChoiceAdd"/>
      </div>
      <q-separator />
      <div>
        <q-btn label="Сохранить" type="submit" color="primary"/>
      </div>
    </q-form>
  </q-card>
</template>

<script>
import { ref, watch } from 'vue'

export default {
  name: 'SurveyEdit',
  props: {
    imageURL: String,
    question: String,
    choices: Array,
    correctAnswer: Number,
  },
  setup(props) {
    const question = ref(props.question || '')
    const choices = ref([...(props.choices || [])])
    const correctAnswerIndex = ref(props.correctAnswer ?? null)
    const localImageURL = ref(props.imageURL || '')
    const loaderModel = ref(null)

    // 监听props变化,同步本地数据
    watch(() => props.question, (newVal) => {
      question.value = newVal || ''
    })

    watch(() => props.choices, (newVal) => {
      choices.value = [...(newVal || [])]
    }, { deep: true })

    watch(() => props.correctAnswer, (newVal) => {
      correctAnswerIndex.value = newVal ?? null
    })

    watch(() => props.imageURL, (newVal) => {
      localImageURL.value = newVal || ''
    })

    return {
      loaderModel,
      imageURL: localImageURL,
      question,
      choices,
      correctAnswerIndex,
  
      onImageLoaded(image) {
        if (image) {
          localImageURL.value = URL.createObjectURL(image)
        }
      },
      onSubmit() {
        console.log(question.value)
        console.log(choices.value)
        console.log(correctAnswerIndex.value)
      },
      onChoiceAdd() {
        choices.value.push('') // 直接push字符串,不要push ref对象
      },
      onDeleteChoice(index) {
        choices.value.splice(index, 1)
        if (index === correctAnswerIndex.value || correctAnswerIndex.value >= choices.value.length ) {
          correctAnswerIndex.value = null
        }
      },
      onSelectCorrectAnswer(index) {
        correctAnswerIndex.value = index
      }
    } 
  },
}
</script>
<style scoped>
.image-loader {
  margin-bottom: 20px;
}
.choices-list {
  padding-left: 0px;
}
.choice {
  margin-bottom: 20px;
}
</style>

额外修复点

  • 移除了onChoiceAdd中错误的ref(''),直接push空字符串,避免v-model绑定异常
  • 简化了表单校验规则,用可选链val?.length替代冗余判断
  • 给v-for循环添加了:key属性,符合Vue最佳实践

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 12:12:02