开发者问题收集

Vue3:如何从组件的 setup() 方法访问 methods 属性内的函数?

2023-08-10
57

我有一个包含将显示的问题数据的数组。一次只能显示一个问题。一旦用户选择了问题的答案,他们就可以通过单击按钮或使用上下箭头键移动到下一个问题。

这是当前版本的 gif: 屏幕录制 gif

App.vue 文件:

import { defineComponent, ref } from 'vue';
import QuestionCard from "./components/QuestionCard.vue";

export default defineComponent({
   components: {
      QuestionCard
   },
   setup() {
      const questionArray = ref([
         {
            id: "123",
            question: "Which of these is a colour in the rainbow?",
            options: [
               'brown', 'red', 'black',
            ],
         }, {
            id: "456",
            question: "How many continents does Earth have?",
            options: [
               1, 7, 6, 9
            ],
         }, {
            id: "789",
            question: "Which of these is a prime number?",
            options: [
               7, 4, 44, 
            ],
         },
      ]);

      let currentQuestion = ref(0);

      return {
         questionArray, currentQuestion
      }
   },
   methods: {
      nextQuestion() {
         if (this.currentQuestion < this.questionArray.length - 1) {
            this.currentQuestion++;
         }
      },
      previousQuestion() {
         if (this.currentQuestion > 0) {
            this.currentQuestion--;
         }
      }
   },
   mounted() {
      console.log('Mounted!!')
      window.addEventListener("keyup", (event) => {
         if (event.code == 'ArrowDown') {
            this.nextQuestion();
         }
         else if (event.code == 'ArrowUp') {
            this.previousQuestion();
         }
      });
   },
   
});
</script>


<template>
   <div>
      <div id="top_bar">
         <button @click="previousQuestion">Previous Question (Up key)</button>
         <button @click="nextQuestion">Next Question (Down key)</button>
      </div>

      <div id="question_section">
         <QuestionCard 
            :question="questionArray[currentQuestion].question"
            :answer_options="questionArray[currentQuestion].options"
         ></QuestionCard>
      </div>
   </div>
</template>

我有一个名为 QuestionCard 的组件,用于显示问题和答案选项。就像在 App 组件中一样,我添加了一个事件监听器,以便检测按键。每个答案选项都有一个键码,以字母 A(ASCII - 65)开头。例如,根据上面 questionArray 中第一个问题的答案选项,第一个问题的 QuestionCard 只能响应 3 个键码:

  • A 代表棕色
  • B 代表红色
  • C 代表黑色

无论是

  1. 按下分配给答案选项按钮的其中一个键,还是
  2. 单击它,

我想记录答案选项值并将其传递给名为 storeAnswer() 的方法。 我通过调用此方法并在单击 <button> 时传递值,实现了目标 1。但是,我不太确定如何实现目标 2。

以下是我所做的 - 在 QuestionCardsetup() 方法中,我已将侦听器添加到窗口以检测按键释放。在检测时,我检查 key 是否与分配给答案选项的任何按键匹配。如果是,我调用 storeAnswer() 方法。但是,我收到一条错误消息:

Uncaught ReferenceError: storeAnswer is not defined

以下是 QuestionCard.vue 的代码:

<script lang="ts">
import { defineComponent, ref } from 'vue'

export default defineComponent({
   props: {
      name: String,
      question: String,
      answer_options: Array,
   },
   methods: {
      storeAnswer(ans: any) { 
         this.selectedAns = ans;
         console.log("Button clicked: " + ans);
      }
   },
   setup(props) {
      let selectedAns = ref(null);
      
      // loop through array of answer_options and assign a key code for each one so that it can be detected
      const keyOptions: {keyCode: string, value: any}[] = [];
      for (let i=0; i < props.answer_options.length; i++) {
         keyOptions.push(
            {
               keyCode: String.fromCharCode(65+i),
               value: props.answer_options[i]
            }
         );
      }

      window.addEventListener("keyup", (event) => {
         const keyPressed = event.key.toUpperCase();

         const i = keyOptions.findIndex(Element => Element.keyCode == keyPressed);
         if (i > -1) {            
            selectedAns.value = keyOptions[i].value;
            console.log(`Option you selected is: ${keyPressed + ')' + selectedAns.value}`)
            storeAnswer();
         }
      });

      return {
         selectedAns
      }
   },
});
</script>


<template>
   <div>
      <h4>{{ question }}</h4>
      <div>
         <ul>
            <li v-for="(option, index) in answer_options" :key="index">
               <button @click="storeAnswer(option)">
                  {{  String.fromCharCode(65 + index) + ') ' + option }}
               </button>
            </li>
         </ul>
      </div>

      <!-- display the selected answer -->
      <p>
         {{ selectedAns }}
      </p>
   </div>
</template>
2个回答

我建议您创建一个可组合函数。像这样定义您的所有函数和应用状态:

 // /src/composables/useQuestions.js 
 import {ref, onMounted, computed} from 'vue'

 const questionArray = ref([])
 const currentQuestion = ref(0)
 const selectedAns = ref(null)
 const computed_answer_options = computed(() => {
    const questionObj = questionArray.value[currentQuestion.value]
    if (questionObj) return questionObj.options
    return []
 })
 const computed_question = computed(() => {
    const questionObj = questionArray.value[currentQuestion.value]
    if (questionObj) return questionObj.question
    return null
 })

 export default () => {
    onMounted(() => {
       window.addEventListener("keyup", (event) => {
          if (event.code == 'ArrowDown') {
             nextQuestion();
          }
          else if (event.code == 'ArrowUp') {
             previousQuestion();
          }
       });
    })

    // here you can call function that detect key releases and calls storeAnswer
    // I did't finish logic, but i think you got the point

    return {
       questionArray,
       currentQuestion,
       nextQuestion,
       previousQuestion,
       computed_answer_options,
       computed_question,
       storeAnswer
    }
 }

 const nextQuestion = () => {
    if (currentQuestion.value < questionArray.value.length - 1) {
       currentQuestion.value++;
    }
 }
 const previousQuestion = () => {
    if (currentQuestion.value > 0) {
       currentQuestion.value--;
    }
 }
 const storeAnswer = (ans) => {
    selectedAns.value = ans 
 }

在您的组件中使用如下:

 // App.vue, QuestionCard.vue
 ...,
 setup () {
   const {
       questionArray,
       currentQuestion,
       nextQuestion,
       previousQuestion,
       computed_answer_options,
       computed_question,
       storeAnswer
    } = useQuestions()
   questionArray.value // returns []
 }
Rassulzhan
2023-08-10

感谢 @Estus Flask ,我成功解决了我的问题,他指出我在代码中误用了 Vue 的 Composition API 和 Options API。

这是有效的代码。

<script setup>
import { ref, onMounted } from 'vue';
import QuestionCard from "./components/QuestionCard.vue";


const questionArray = ref([
   {
      id: "123",
      question: "Which of these is a colour in the rainbow?",
      options: [
         'brown', 'red', 'black',
      ],
      selectedAns: null,
   }, {
      id: "456",
      question: "How many continents does Earth have?",
      options: [
         1, 7, 6, 9
      ],
      selectedAns: 6,
   }, {
      id: "789",
      question: "Which of these is a prime number?",
      options: [
         7, 4, 44, 
      ],
      selectedAns: 4,
   },
]);

const answerOptions = ref([]);
const currentQuestion = ref(0);


// methods to use in the App component -----------------------------------------------------------------
function nextQuestion() {
   if (currentQuestion.value < questionArray.value.length - 1) {
      currentQuestion.value++;
      setAnswerOptions();
   }   
}
function previousQuestion() {
   if (currentQuestion.value > 0) {
      currentQuestion.value--;
      setAnswerOptions();
   }
}
function setAnswerOptions() {
   answerOptions.value.length = 0;

   for (let i=0; i < questionArray.value[currentQuestion.value].options.length; i++) {
      answerOptions.value.push(
         {
            key: String.fromCharCode(65+i),
            value: questionArray.value[currentQuestion.value].options[i]                  
         }
      );
   }
}


// lifecycle hooks--------------------------------------------------------------------------------
onMounted(() => {
   // console.log('App mounted, setting up listeners!!');

   setAnswerOptions();

   window.addEventListener("keyup", (event) => {
      if (event.code == 'ArrowDown') {
         nextQuestion();
      }
      else if (event.code == 'ArrowUp') {
         previousQuestion();
      }
      
      const i = answerOptions.value.findIndex(element => element.key == event.key.toUpperCase());
      if (i > -1) {
         // console.log(`You have seleced ${answerOptions.value[i].value}, moving to next question`);
         questionArray.value[currentQuestion.value].selectedAns = answerOptions.value[i].value;
         nextQuestion();
      }
   });   

   // console.log('listeners all set!')
});
</script>


<template>
   <div>
      <div id='top_bar'>
         <button @click=" previousQuestion ">Previous Question (Up key)</button >
         <button @click=" nextQuestion ">Next Question (Down key)</button >
      </div>

      <div id='question_section'>
         <QuestionCard 
            :id=" questionArray[currentQuestion].id "
            :question=" questionArray[currentQuestion].question "
            :answer_options=" questionArray[currentQuestion].options "
         ></QuestionCard>
         
         <p v-if=" questionArray[currentQuestion].selectedAns != null ">
            {{ 'Your last answer was: ' + questionArray[currentQuestion].selectedAns }}
         </p>
         
         <div id='answer_options_section'>
            <ul>
               <li v-for=" option in answerOptions ">
                  <button :class=" {'selected-option': option.value == questionArray[currentQuestion].selectedAns } " >
                     {{  option.key + ') ' + option.value }}
                  </button>
               </li>
            </ul>
         </div>
      </div>
   </div>
</template>

QuestionCard.vue

<script setup>
import { ref } from 'vue'

// define the props used in this component
const props = defineProps({
   id: String,
   name: String,
   question: String,
   answer_options: Array,
});
</script>


<template>
   <div>
      <h5>Question ID: {{ id }}</h5>
      <h4>{{ question }}</h4>
   </div>
</template>
sweet-pineapple
2023-08-12