pages/api/quiz.tsx 에서 게시글을 읽어들이는 로직을 작성하는데,
이미 데이터를 읽는 로직은 handler의 최상위에 있기에 아래의 부분만 작성하면 데이터를 반환하게 된다.
} else if (req.method === "GET") {
res.status(200).json({
message: "퀴즈를 불러왔습니다.",
result: {
quizes: quizes,
keywords: keywords,
},
});
} else {
res.status(405).json({ message: "메서드가 잘못 되었습니다." });
}
이렇게 데이터를 반환했을 때,
json으로 작성한 부분이 'data'에 담겨서 반환된다.
즉 응답객체는 아래와 같다.
res : {
status : 200,
data : {
message: "퀴즈를 불러왔습니다.",
result: {
quizes : Array<quiz>,
keywords ; Array<string>,
},
},
}
버튼을 누르면 응답을 받아 콘솔을 찍는 코드로 콘솔을 찍어보면 아래와 같다.
import axios from "axios";
import React from "react";
const QuizPage = () => {
const onClick = async (e) => {
e.preventDefault();
try {
const res = await axios.get("/api/quiz");
if (res.status === 200) {
alert(res.data.message);
console.log(res);
}
} catch (e) {
alert(e.message);
}
};
return (
<div>
<button type="button" onClick={onClick}>
{" "}
전송보내기{" "}
</button>
</div>
);
};
export default QuizPage;
이제 useEffect로 퀴즈를 불러오고,
무작위로 한 문제를 선정하도록 컴포넌트를 수정한다.
import axios from "axios";
import React, { useEffect, useState } from "react";
import { Quiz } from "../api/quiz";
const QuizPage = () => {
const [quizes, setQuizes] = useState([]);
const [keywords, setKeywords] = useState([]);
const [quizNumber, setQuizNumber] = useState(0);
const fetchData = async () => {
try {
const res = await axios.get("/api/quiz");
if (res.status === 200) {
const quizes = res.data.result.quizes as Quiz[];
const keywords = res.data.result.keywords as string[];
return { quizes, keywords };
}
} catch (e) {
alert(e.message);
}
};
useEffect(() => {
fetchData().then((res) => {
const { quizes, keywords } = res;
setQuizes(quizes);
setKeywords(keywords);
setQuizNumber(Math.floor(Math.random() * quizes.length));
});
}, []);
return (
<div>
<div>{JSON.stringify(quizes[quizNumber])}</div>
<div>{JSON.stringify(keywords)}</div>
</div>
);
};
export default QuizPage;
나머지 로직은 테스트의 용이성 및 로직 관리의 용이성을 위해 자식 컴포넌트를 만들어서 진행해야 할 것 같다.
문제풀이 컴포넌트
components/QuizContainer.tsx 이다.
퀴즈앱3 의 로직을 대부분 따라가지만,
'n개의 정답 키워드에 무작위 키워드를 추가해서 10개의 보기 키워드를 만드는 로직'과
'n개의 정답 키워드 각각이 정답 상태인지 아닌지(isCorrected)'
'isCorrected 상태를 바탕으로 현재 풀고 있는 문제가 몇 번 문제인지' 가 추가되었다.
(이때 퀴즈앱 3의 로직은 useEffect 안에 넣어 한 번만 실행 되도록 하며,
Quiz 프롭스가 로딩되지 않을 경우에 대비하여 빠른 반환 및 의존자 배열에 추가해준다.)
import React, { useEffect, useState } from "react";
import { Quiz } from "../pages/api/quiz";
const QuizContainer = ({
quiz,
keywords,
}: {
quiz: Quiz;
keywords: string[];
}) => {
const [slicedTextArray, setSlicedTextArray] = useState([] as string[]);
const [replacedTextArray, setReplacedTextArray] = useState([] as string[]);
const [answersKeyword, setAnswersKeyword] = useState([] as string[]);
const [allAnswersKeyword, setAllAnswersKeyword] = useState([] as string[]);
const [isCorrect, setIsCorrect] = useState([] as boolean[]);
useEffect(() => {
const randomIndexes = [];
if (!quiz) return;
const { keywordArray, originalText, category, title } = quiz;
while (randomIndexes.length < 5) {
// 배열 인덱스 중 무작위 5개의 인덱스를 선택한다.
const randomIndex = Math.floor(Math.random() * keywordArray.length);
if (!randomIndexes.includes(randomIndex)) {
randomIndexes.push(randomIndex);
}
}
// 인덱스들을 오름차순으로 정렬한다.
randomIndexes.sort((a, b) => a - b);
// 해당하는 인덱스의 키워드들을 배열로 반환한다.
const sortedKeyword = randomIndexes.map((index) => {
return keywordArray[index];
});
console.log(JSON.stringify(sortedKeyword));
let fromIndex = 0;
const answersKeyword = [];
const answersIndex = [];
sortedKeyword.forEach((value) => {
let target = value;
let indexResult = originalText.indexOf(target, fromIndex);
if (indexResult < 0) target = value.split(" ").join("");
indexResult = originalText.indexOf(target, fromIndex);
if (indexResult >= 0) {
answersKeyword.push(target);
answersIndex.push(indexResult);
fromIndex = indexResult + 1;
}
});
const slicedTextArray = [];
const replacedTextArray = [];
answersIndex.forEach((answerIndex, idx) => {
//만약 slicedTextArray가 없다면, 최초 키워드의 인덱스 전까지의 모든 문자열을 배열에 삽입한다.
if (!slicedTextArray.length) {
slicedTextArray.push(originalText.slice(0, answerIndex));
}
//현재 키워드의 인덱스부터 다음 키워드의 인덱스까지를 slice한다. 만일 다음 키워드가 없다면 끝까지 슬라이스 된다.
const slicedText = originalText.slice(answerIndex, answersIndex[idx + 1]);
//슬라이스한 문자열에서 키워드를 찾아 키워드를 '{0번 문제}'로 바꾼다.
const replacedText = slicedText.replace(
answersKeyword[idx],
`\{문제 ${idx + 1}번\}`
);
//바꾼 문자열을 배열에 추가한다.
slicedTextArray.push(slicedText);
replacedTextArray.push(replacedText);
});
//배열을 모두 합치면 원하는 문자열이 된다.
setIsCorrect(Array(replacedTextArray.length).fill(false));
setSlicedTextArray(slicedTextArray);
setReplacedTextArray(replacedTextArray);
setAnswersKeyword(answersKeyword);
const allAnswersKeyword = [...answersKeyword];
while (allAnswersKeyword.length < 10) {
const idx = Math.floor(Math.random() * keywords.length);
const keyword = keywords[idx];
if (!allAnswersKeyword.includes(keyword)) allAnswersKeyword.push(keyword);
}
setAllAnswersKeyword(allAnswersKeyword.sort(() => Math.random() - 0.5));
}, [quiz]);
const [answerIndex, setAnswerIndex] = useState(0);
useEffect(() => {
if (!isCorrect.length) return;
let allCorrected = true;
for (let i = 0; i < 5; i++) {
if (!isCorrect[i]) {
setAnswerIndex(i);
allCorrected = false;
break;
}
}
if (allCorrected) setTimeout(() => alert("모두 맞췄습니다!"), 50);
}, [isCorrect]);
const onClickKeyword = (e) => {
if (e.target.value === answersKeyword[answerIndex]) {
const newIsCorrect = [...isCorrect];
newIsCorrect[answerIndex] = true;
e.target.disabled = true;
setIsCorrect(newIsCorrect);
} else {
alert("틀렸습니당");
}
};
return (
<div>
<div style={{ whiteSpace: "pre" }}>
{[
slicedTextArray[0],
...isCorrect.map((boolean, idx) => {
if (boolean) return slicedTextArray[idx + 1];
else return replacedTextArray[idx];
}),
].join("")}
</div>
<div>
{allAnswersKeyword.map((keyword) => {
return (
<button type="button" value={keyword} onClick={onClickKeyword}>
{keyword}
</button>
);
})}
</div>
</div>
);
};
export default QuizContainer;
결과물은 아래와 같다.
지금도 이미 문제풀이앱이 된 것 같다..(걍 배포할까?)