Vue.js中如何汇总所有单选按钮组选中值生成统一数组
问题根源
你现在的bug本质是Vue组件的作用域隔离规则:你在Question.vue的data里定义的arr数组,是每个Question组件实例独立持有的本地状态。你渲染了多少道题,就会生成多少个独立的Question实例,每个实例初始化时都会把自己的arr重置为[0,0,0,0,0],实例之间状态完全不共享,自然没法汇总所有题目的选择结果。
另外还有两个直接影响功能的附加问题:
- 所有单选框的
name属性都绑定成了同一个choices数组,会导致跨题目的单选互斥,根本无法实现每组题单独选一个选项的基础效果 onChange方法里e.target.name == i.choices的判断逻辑永远不会成立,等号两边一边是字符串类型、一边是数组类型,全等判断恒为false
修复方案
按照Vue单向数据流的规范,跨组件共享的汇总状态要上提到最顶层的公共祖先组件,也就是你的App.vue中,再通过props向下传递状态,通过自定义事件向上通知更新,不要在最底层的子组件里维护全局汇总数据。
具体修改步骤如下:
1. 修改App.vue
在根组件初始化答案数组,同时添加接收子组件更新答案的方法,将状态和方法传递给下层组件:
<template> HELLO <Panels :questions="questions" :answerArr="answerArr" @update-answer="updateAnswer"/> </template> <script> import Panels from './components/Panels.vue'; import data from './questions.json'; export default{ data(){ return{ questions: data, // 初始化和题目数量等长的答案数组,默认值填0即可 answerArr: new Array(data.length).fill(0) } }, methods: { updateAnswer(index, value) { // 用splice做响应式更新,不要直接通过索引修改数组 this.answerArr.splice(index, 1, value) console.log('当前所有题的答案:', this.answerArr) } }, components: { Panels } } </script> <style> </style>
2. 修改Panels.vue
只做props和自定义事件的透传即可,不要添加额外业务逻辑:
<template> <div v-for="(question, index) in questions" :key="question.id"> <Panel :question="question" :questionIndex="index" :answerArr="answerArr" @update-answer="(idx, val) => $emit('update-answer', idx, val)" /> </div> </template> <script> import Panel from './Panel.vue' export default{ name: 'Panels', props: { questions: Array, answerArr: Array }, components: { Panel } } </script> <style scoped> </style>
注意:v-for必须绑定唯一key,优先使用数据自带的唯一id字段,不要直接用索引当key,避免出现渲染错乱bug
3. 修改Panel.vue
同样做状态和事件透传,把当前题目的索引传给最底层的Question组件:
<template> <div class = "Panel"> <div> <p>{{question.question}}</p> <Question :choices="question.choices" :questionIndex="questionIndex" :correctAnswer="question.correctAnswer" @update-answer="(idx, val) => $emit('update-answer', idx, val)" /> <p>当前题选择:{{$root.answerArr[questionIndex]}}</p> </div> </div> </template> <script> import Question from './Question.vue'; export default{ name: 'Panel', props: { question: Object, questionIndex: Number, answerArr: Array }, components: { Question } } </script> <style scoped> </style>
4. 重写Question.vue
删掉本地维护全局arr的逻辑,只维护当前题目的单选状态,选中时触发事件向上传值即可,同时修复单选框name重复的问题:
<template> <div v-for="choice in choices" :key="choice"> <!-- name绑定当前题的索引,保证同一题单选互斥、不同题互不干扰 --> <input type="radio" :id="`q-${questionIndex}-${choice}`" v-model="mChoice" :name="`question-${questionIndex}`" :value="choice" @change="onChange" > <label :for="`q-${questionIndex}-${choice}`">{{choice}}</label><br> </div> <p>当前选中:{{mChoice}}</p> </template> <script> export default{ name: 'Question', data(){ return{ mChoice: null } }, methods: { onChange(){ // 直接把当前题索引和选中值抛给上层,不需要在子组件遍历所有题目 this.$emit('update-answer', this.questionIndex, { idx: this.questionIndex, selected: this.mChoice, isCorrect: this.mChoice === this.correctAnswer }) } }, props: { choices: Array, questionIndex: Number, correctAnswer: String } } </script> <style scoped> </style>
额外注意点
- 你原有代码里的
<style scope>是拼写错误,Vue的样式作用域关键字是scoped,拼错会导致样式隔离完全不生效 - 子组件不要直接修改父组件/根组件传入的props,所有共享状态的更新都要通过事件通知上层修改,不然会出现响应式逻辑混乱的问题
- 多组单选框必须给每组设置独立的
name属性,不然浏览器默认的单选互斥逻辑会跨组生效,选了第二题的选项就会清空第一题的选中状态
内容的提问来源于stack exchange,提问作者chree
相关产品推荐
相关产品推荐

