Vue3异步获取API数据时前端不显示问题求助
Vue3 问答应用异步获取数据前端不渲染问题
问题描述
开发问答应用时,使用静态数组存储问题,前端可正常显示;但通过异步API获取数据时,控制台能打印出问题数据,前端却无法渲染。重点查看Home.vue中的fetchQuestionsFromServer函数。
相关代码
Home.vue
<template> <main class="flex h-screen items-center justify-center bg-gray-100"> <!-- quiz container --> <QuizComplatePage v-if="endofQuiz" /> <div class="overflow-hidden bg-white flex-none container relative shadow-lg rounded-lg px-12 py-6" > <img src="@/assets/images/abstract.svg" alt="" class="absolute -top-10 left-0 object-none" /> <!-- contents --> <div class="relative z-20"> <!-- score container --> <div class="text-right text-gray-800"> <p class="text-sm leading-3">Score</p> <p class="font-bold">{{score}}</p> </div> <!-- timer container --> <div class="bg-white shadow-lg p-1 rounded-full w-full h-5 mt-4"> <div class="bg-blue-700 rounded-full w-11/12 h-full" :style= "`width: ${timer}%`" ></div> </div> <!-- question container --> <div class="rounded-lg bg-gray-100 p-2 neumorph-1 text-center font-bold text-gray-800 mt-8" > <div class="bg-white p-5"> {{currentQuestion.question}} </div> </div> <!-- options container --> <div class="mt-8"> <!-- option container --> <div v-for="(choice,item) in currentQuestion.choices" :key="item"> <div class="neumorph-1 option-default bg-gray-100 p-2 rounded-lg mb-3 relative" :ref="optionchosen" @click="onOptionClick(choice,item)" > <div class="bg-blue-700 p-1 transform rotate-45 rounded-md h-10 w-10 text-white font-bold absolute right-0 top-0 shadow-md" > <p class="transform -rotate-45">+10</p> </div> <div class="rounded-lg font-bold flex p-2"> <!-- option ID --> <div class="p-3 rounded-lg">{{item}}</div> <!-- option name --> <div class="flex items-center pl-6">{{choice}}</div> </div> </div> </div> <!-- option container --> </div> <!-- progress indicator container --> <div class="mt-8 text-center"> <div class="h-1 w-12 bg-gray-800 rounded-full mx-auto"></div> <p class="font-bold text-gray-800">{{questionCounter}}/{{questions.length}}</p> </div> </div> </div> </main> </template> <script> import { onMounted, ref } from 'vue' import QuizComplatePage from './QuizCompleteOverlay.vue' export default{ components: { QuizComplatePage }, setup(){ //data let canClick = true let score = ref(0) let timer = ref(100) let endofQuiz = ref(false) let questionCounter = ref(0) const currentQuestion = ref({ question: '', answer: 1, choices: [], }); const questions = [] const loadQuestion = () =>{ canClick = true timer.value=100 //Check question array had questions or not if(questions.length > questionCounter.value){ currentQuestion.value = questions[questionCounter.value] console.log('Current Question is : ', currentQuestion.value); questionCounter.value++ }else{ endofQuiz.value = true console.log('Out of Questions'); } } //methods let itemsRef = [] const optionchosen = (element) =>{ if(element){ itemsRef.push(element) } } const clearSelected = (divselected) =>{ setTimeout(()=>{ divselected.classList.remove('option-correct') divselected.classList.remove('option-wrong') divselected.classList.add('option-default') loadQuestion() },1000) } const onOptionClick = (choice,item) =>{ if(canClick) { const divContainer = itemsRef[item] const optionId = item+1 if(currentQuestion.value.answer ===optionId){ console.log('You are Correct'); score.value += 10 divContainer.classList.add('option-correct') divContainer.classList.remove('option-default') }else{ console.log('You are Wrong'); divContainer.classList.add('option-wrong') divContainer.classList.remove('option-default') } timer.value=100 canClick=false //to go next question clearSelected(divContainer) console.log(choice,item); }else{ console.log('Cant Select Option'); } } const countDownTimer = () =>{ let interval= setInterval(()=>{ if(timer.value>0){ timer.value-- }else{ console.log('Time is over'); clearInterval(interval) } },150) } const fetchQuestionsFromServer = async function(){ fetch('https://opentdb.com/api.php?amount=10&category=18&type=multiple') .then((res) =>{ return res.json() }) .then((data) =>{ const newQuestions = data.results.map((serverQuestion) =>{ const arrangedQuestion = { question: serverQuestion.question, choices: '', answer: '' } const choices = serverQuestion.incorrect_answers arrangedQuestion.answer = Math.floor(Math.random() * 4 + 1) choices.splice(arrangedQuestion.answer-1 , 0 , serverQuestion.correct_answer) arrangedQuestion.choices = choices return arrangedQuestion }) console.log('new formated questions' , newQuestions); questions.value = newQuestions loadQuestion() countDownTimer() console.log('questions: =>' , questions.value); }) } //lifecycle hooks onMounted(() =>{ fetchQuestionsFromServer() }) //return return { timer, currentQuestion, questions, score, questionCounter, loadQuestion, onOptionClick, optionchosen, endofQuiz, } } } </script> <style scoped> .neumorph-1 { box-shadow: 6px 6px 18px rgba(0, 0, 0, 0.09), -6px -6px 18px #ffffff; } .container { max-width: 400px; border-radius: 25px; } </style>
QuizCompletePage.vue
<template> <div class="w-screen h-screen absolute z-30 bg-white bg-opacity-30 flex justify-center items-center"> <div class="bg-green-700 p-4 text-center text-white"> <p class="font-bold text-2xl">All DOne!</p> <p class="my-4 font-bold text-3xl">100% Score</p> <!-- Buttons --> <div class="flex justify-center"> <div class="rounded-full py-1 w-28 border cursor-pointer hover:text-black hover:bg-white">Done</div> <div class="rounded-full py-1 w-28 border cursor-pointer hover:text-black hover:bg-white">Retry</div> </div> </div> </div> </template> <script> export default { name: 'QuizComplatePage' } </script> <style> </style>
问题截图

解决方案
核心问题是questions数组未被声明为响应式数据:
- 当前代码中
const questions = []是普通数组,Vue无法监听其变化,异步赋值后无法触发视图更新。 - 将
questions修改为ref包裹的响应式数组即可:
// 原代码 const questions = [] // 修改后 const questions = ref([])
原理:Vue3中,普通对象/数组不会自动成为响应式数据,必须通过ref或reactive包装,Vue才能追踪其变化并更新视图。静态数组能正常显示是因为初始化时数据已存在,而异步获取数据是后续赋值,普通数组无法触发响应式更新。
内容的提问来源于stack exchange,提问作者Somasekhar
相关产品推荐
相关产品推荐

