포켓몬 약점 계산기 (poke-match-type)
- 프론트엔드 : React, TypeScript
- 팀 : 1인 개발
- 깃허브 :https://github.com/changchangwoo/POKE-MATCH-TYPE
- 배포주소 : https://poke-match-type.vercel.app/
- 타입스크립트를 활용한 구현
- 학습한 라이브러리 접목
— tanstackQuery를 활용한 통신 데이터 캐싱, 동기화
— useHookForm을 활용한 입력 데이터 관리- 모바일 우선 구현 + 반응형
- 프론트엔드 서버 배포
const filteredSuggestions = pokemonNames.filter((pokemon) => {
return pokemon.name.startsWith(searchTerm);
});
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (suggestions.length > 0) {
if (event.key === "ArrowDown") {
setActiveSuggestionIndex((prevIndex) =>
prevIndex === suggestions.length - 1 ? 0 : prevIndex + 1
);
} else if (event.key === "ArrowUp") {
setActiveSuggestionIndex((prevIndex) =>
prevIndex === 0 ? suggestions.length - 1 : prevIndex - 1
);
} else if (event.key === "Enter") {
if (
activeSuggestionIndex >= 0 &&
activeSuggestionIndex < suggestions.length
) {
handleSuggestionClick(suggestions[activeSuggestionIndex]);
}
}
}
};
검색 기능에서 가장 고민한 것은 연관 검색어에 필요한 데이터의 추출이였다.
pokeAPI에서 한글 도감 리스트를 제공하지 않았기 때문이다.
물론 한글 정보를 제공하지만 이를 위해서는 포켓몬 개별 ID로 접근하여 데이터를 통신해야하기때문에 연관 검색을 구현 하기 위해서는 불필요한 요청들이 다수 필요했다.
이를 해결하기 위해 Cheerio라이브러리를 활용한 스크래핑을 통해 포켓몬 도감 사이트에서 다음과 같은 검색에 필요한 데이터를 직접 추출하였다
/* 스크래핑 코드 추가*/
/* pokemonData.json */
[
{
"no": 1, // 도감번호
// (데이터 요청시 pokeAPI에서 해당 번호에 따라 구분된 데이터를 제공한다)
"name": "이상해씨" // 연관검색어에 출력하는 데이터
},
{
"no": 2,
"name": "이상해풀"
},
{
"no": 3,
"name": "이상해꽃"
},
.
.
]
/* SelectType.tsx */
const SelectType = ({ checkedType, setCheckedType }: SelectTypeProps) => {
const handleSelect = (type: any) => {
const isAlreadyChecked = checkedType.some(
(checked) => checked.typeNo === type.no
);
if (isAlreadyChecked) {
setCheckedType(
checkedType.filter((checked) => checked.typeNo !== type.no)
);
} else {
if (checkedType.length >= 2) return;
setCheckedType([...checkedType, { typeNo: type.no, name: type.name }]);
}
};
/* getDetailType.ts */
export const getDetailType = async (searchTypes: number[]) => {
const initialTypes: IDamageData[] = JSON.parse(
JSON.stringify(defaultTypesData)
); // 데미지 경추감이 반영되지 않은 퓨어한 JSON 형식의 데이터를 받아온다
const fetchAllDetails = async () => {
const detailPromises = await fetchDetailType(searchTypes);
const detailResponses = await Promise.all(detailPromises);
// pokeAPI로 부터 타입 별 데미지 연관 데이터들을 가공하여 받아온다
const allDamageRelations = detailResponses.flat();
await getCirculType(initialTypes, allDamageRelations);
// 퓨어한 데이터들의 데미지 연관 데이터를 적용시킨다
return initialTypes;
};
const getCirculType = async (
updateTypes: IDamageData[],
damageRelations: IDamageRelations[]
) => {
for (let relation of damageRelations) {
relation.types.forEach((element) => {
const typeToUpdate = updateTypes.find(
(type) => type.name === element.name
);
if (typeToUpdate) {
switch (relation.key) {
case "doubleDamage":
typeToUpdate.damage *= 2;
break;
case "halfDamage":
typeToUpdate.damage *= 0.5;
break;
case "noDamage":
typeToUpdate.damage *= 0;
break;
}
}
});
}
};
const detailTypes = await fetchAllDetails();
return detailTypes;
};
코드를 충분하게 짠 것 같아도 데미지 데이터 계산 값을 올바르게 반환하지 않는 이슈를 겪었다. 이는 해당 기능이 비동기적인 방식으로 데이터를 요청하고, 가공하는 과정을 가지고 있기에 비동기 순서를 명시하는 await-async 코드의 흐름이 중요했기 때문이었다. 각 요청별로 콘솔을 찍는 디버깅 후 흐름을 파악하여 수정함으로 해결할 수 있었다.
/* getAddAbility.ts */
type typeCalculatorType = { type: string; effects: number };
export const getAddAbility = async (
types: IDamageData[],
selectedAbility: string
) => {
const typeCalculator: typeCalculatorType[] = [];
switch (selectedAbility) {
case "dry_skin":
typeCalculator.push({ type: "fire", effects: 1.25 });
typeCalculator.push({ type: "water", effects: 0 });
break;
case "heatproof":
typeCalculator.push({ type: "fire", effects: 0.5 });
break;
.
. // 20여개의 특성에 따른 계산값
.
}
if (typeCalculator.length > 0) {
types = types.map((type) => {
typeCalculator.forEach((element) => {
if (type.name === element.type) {
type.damage *= element.effects;
}
});
return type;
});
}
return typeCalculator;
};
/* typeCard.tsx */
useEffect(() => {
const fetchData = async () => {
let result = await getDetailType(typeNo);
if(selectedAbility && selectedAbility !== "") {
getAddAbility(result, selectedAbility);
}
let groupResult = await getGroupType(result);
setTypeRelations(groupResult);
};
fetchData();
}, [MatchTypes, selectedAbility]);