Vue3:如何从组件的 setup() 方法访问 methods 属性内的函数?
我有一个包含将显示的问题数据的数组。一次只能显示一个问题。一旦用户选择了问题的答案,他们就可以通过单击按钮或使用上下箭头键移动到下一个问题。
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 代表黑色
无论是
- 按下分配给答案选项按钮的其中一个键,还是
- 单击它,
我想记录答案选项值并将其传递给名为
storeAnswer()
的方法。
我通过调用此方法并在单击
<button>
时传递值,实现了目标 1。但是,我不太确定如何实现目标 2。
以下是我所做的 - 在
QuestionCard
的
setup()
方法中,我已将侦听器添加到窗口以检测按键释放。在检测时,我检查
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>
我建议您创建一个可组合函数。像这样定义您的所有函数和应用状态:
// /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 []
}
感谢 @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>