Vue2测验组件编译与挂载报错求助:模板根元素与监听问题
Vue2测验组件两处错误的修复方案
错误1:模板编译警告(组件模板必须包含且仅包含一个根元素)
原因
Vue2组件模板要求只能有一个顶级根节点,你的模板直接并列了问题容器、结果容器、错题容器三个独立div,不符合语法规范。
修复方法
将所有模板内容包裹在一个根<div>内,确保整个模板只有一个顶级元素。
错误2:mounted钩子中的TypeError(无法读取null的addEventListener属性)
原因
- 元素选择器不匹配:比如
selectAnswer方法里用document.querySelectorAll('.answers span'),但模板里的选项span类名是answer,无.answers父容器,导致找不到元素;全局使用document.querySelector容易超出组件范围,应该用组件内的this.$el.querySelector。 - 初始隐藏元素无法获取:
wrongAnswersBtn初始时wrongAnswers为0,被v-show隐藏,mounted阶段DOM中不存在该元素,querySelector返回null,调用addEventListener直接报错。 - 函数上下文错误:
calculateResult用箭头函数定义,导致this不指向Vue实例,无法访问组件数据。 - 字段名不一致:questions数组里的正确答案字段是
correctAnswer,但代码里多处写成correct_answer,判断逻辑失效。 - 方法变量错误:
backBtn方法里的this.answer未定义,应该操作this.currentQuestion或this.counter。
修复方法
- 替换所有
document.querySelector为this.$el.querySelector,仅在组件DOM范围内查找元素。 - 改用Vue原生事件绑定(
@click)替代原生addEventListener,Vue会自动处理元素存在性问题,避免null报错。 - 将
calculateResult改为普通函数,确保this指向Vue实例。 - 统一字段名:将questions里的
correctAnswer改为correct_answer,或代码判断逻辑改为question.selected == question.correctAnswer。 - 修正
backBtn方法的变量错误,改为操作this.currentQuestion。
修正后的完整代码
HTML部分
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <div id="app1"> <display-question></display-question> </div>
JS部分
Vue.component('display-question',{ data: function(){ return{ counter: 0, currentQuestion: 0, answered: 0, showWrongQuestion: false, wrongQuestions: [], wrongAnswers: 0, correctAnswers: 0, message: "Enter your answer here", WhatAnswer: "default", questions: [ { question: "What is 2+4 = ?", answer:[ '2','4','6','8' ], correct_answer: 2, selected: null, sense: 0 }, { question: "What is 22+44 = ?", answer:[ '33','44','55','66' ], correct_answer: 3, selected: null, sense: 0 } ] } }, methods: { backBtn: function () { if (this.currentQuestion > 0) { this.currentQuestion -= 1; const nextBtn = this.$el.querySelector('.next-btn'); if(nextBtn) nextBtn.setAttribute('disabled', ''); } }, calculateResult: function(questions) { let correct = 0; for(let i=0; i< questions.length; i++) { if(this.questions[i].selected == questions[i].correct_answer) { correct++; } } return (correct / questions.length) * 100; }, selectAnswer: function(a) { const choice = a.currentTarget; const answers = this.$el.querySelectorAll('.answer'); const nextBtn = this.$el.querySelector('.next-btn'); answers.forEach(answer => { if(answer.classList.contains('selected')) { answer.classList.remove('selected'); } }); choice.classList.add('selected'); this.questions[this.currentQuestion].selected = parseInt(choice.dataset.index); if(nextBtn) nextBtn.removeAttribute('disabled'); }, handleNextClick() { this.answered < this.questions.length ? this.answered++ : ''; if(this.currentQuestion < (this.questions.length -1)) { this.currentQuestion++; const answers = this.$el.querySelectorAll('.answer'); answers.forEach(answer => { if(answer.classList.contains('selected')) { answer.classList.remove('selected'); } }); const nextBtn = this.$el.querySelector('.next-btn'); if(nextBtn) nextBtn.setAttribute('disabled', ''); } else if(this.currentQuestion >= (this.questions.length -1)) { this.questions.forEach( (question) => { if(question.selected == question.correct_answer && question.sense ==0) { this.correctAnswers++; question.sense = 1; } else if(question.selected != question.correct_answer && question.sense ==0) { this.wrongAnswers++; question.sense = 1; this.wrongQuestions.push({ answers: question.answer, question: question.question, correct_answer: question.correct_answer, selected: question.selected }); } }); const result = this.$el.querySelector('.result'); const questionContainer = this.$el.querySelector('.question-container'); if(result) result.classList.add('active'); if(questionContainer) questionContainer.classList.add('blur'); } }, handleCloseResult() { const result = this.$el.querySelector('.result'); const questionContainer = this.$el.querySelector('.question-container'); if(result) result.classList.remove('active'); if(questionContainer) questionContainer.classList.remove('blur'); }, handleWrongAnswersClick() { const result = this.$el.querySelector('.result'); const wrongQuestions = this.$el.querySelector('.wrong-questions'); if(result) result.classList.remove('active'); if(wrongQuestions) wrongQuestions.classList.add('active'); }, handleShowMyResults() { const result = this.$el.querySelector('.result'); const wrongQuestions = this.$el.querySelector('.wrong-questions'); if(result) result.classList.add('active'); if(wrongQuestions) wrongQuestions.classList.remove('active'); } }, template: ` <div class="quiz-container"> <div class="question-container" v-if="counter < questions.length"> <h2>Question No. {{counter+1}} : {{questions[counter].question}}</h2> <br> <span class="answer" v-for="(answer, index) in questions[currentQuestion].answer" :key="index" :data-index="index" @click="selectAnswer">{{ answer }}</span> <p> <button @click="backBtn">BACK</button> <button class="next-btn" disabled @click="handleNextClick">{{ currentQuestion < (questions.length -1) ? "Next" : "Result!" }}</button> </p> </div> <div class="result"> <div class="success"></div> <h2>You have successfully finished the quiz, and your score is:</h2> <h1 :class="[(Number(((correctAnswers / questions.length)*100)).toFixed(2) >= 50) ? 'green' : 'red']">{{ Number(((correctAnswers / questions.length) *100)).toFixed(2) }}%</h1> <small><b>{{ correctAnswers }}</b>Correct,<b>{{ wrongAnswers }}</b>Wrong</small> <button class="close" @click="handleCloseResult">close</button> <button class="show-wrong-ones" v-show="wrongAnswers > 0" @click="handleWrongAnswersClick">Wrong answers</button> </div> <div class="wrong-questions"> <h2 v-if="wrongQuestions.length > 1">Your wrong Questions</h2> <h2 v-else-if="wrongQuestions.length == 1">Your wrong Question</h2> <div class="wrong-one" v-for="question in wrongQuestions" :key="question.question"> <h3>{{ question.question }}</h3> <div class="answers-container"> <span class="selected">{{ question.answers[question.selected] }}</span> <span class="correct">{{question.answers[question.correct_answer] }}</span> </div> </div> <button id="return-to-result" @click="handleShowMyResults">Show my result</button> </div> </div> `, }) var test1 = new Vue({ el: "#app1", data: {}, });
内容的提问来源于stack exchange,提问作者James Pumkin
相关产品推荐
相关产品推荐

