지역 정보는 위키피디아 API를 사용하기로 결정하였다.
위키백과에 지역 이름(예: '치앙마이')을 검색했을 때 제일 위쪽에 나오는 설명을 가져오는 함수를 만들어서 지역 모달에 뿌려주기로 하였다.


Axios를 이용해 위키피디아 요약 정보를 받아오는 API 함수를 구현하면서 잘 몰랐던 내용들을 정리하기로 하였다.
위키피디아 요약 정보를 가져오는 API 함수는 다음과 같이 작성하였다.
// information.api.ts
import axios from "axios";
import { PageData, WikipediaApiResponse } from "../types/pageData.type";
const WIKIPEDIA_URL = "https://ko.wikipedia.org";
export const getPlaceInformation = async (placeName: string) => {
const url = `${WIKIPEDIA_URL}/w/api.php`;
const params = {
action: "query", // 문서 정보 조회
prop: "extracts", // 문서의 요약(본문)을 가져옴
format: "json", // JSON 형식
origin: "*", // CORS 우회
exintro: "true", // 첫 섹션만 가져오기
titles: placeName, // 검색할 문서의 제목
};
try {
const response = await axios.get(url, { params });
const wikipediaData: WikipediaApiResponse = response.data;
const data: PageData = Object.values(wikipediaData.query.pages)[0];
const textData = data.extract;
if (!textData) {
console.warn(`[getPlaceInformation] ${placeName} 문서는 있지만 본문 없음`);
return "설명 없음";
}
const result = `<div class="flex flex-col gap-y-[10px] leading-[1.6]">${textData}</div>`;
return result;
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response?.status === 404) {
console.warn(`[getPlaceInformation] ${placeName}`에 대한 위키피디아 문서 없음);
return "설명 없음",
} else {
console.error("[getPlaceInformation] Axios 에러: ", error.message);
}
} else {
console.error("[getPlaceInformation] 일반 에러: ", error);
}
throw error;
}
};
그리고 CityInfo라는 컴포넌트에서 이 함수를 호출하여 사용하고 있다.
// CityInfo.tsx
import { useQuery } from "@tanstack/react-query";
import { getPlaceInformation } from "../../apis/information.api";
import { WIKIPEDIA_SEARCH_WORD } from "../../constants/wikipedia";
import DOMPurify from "dompurify";
interface CityInfoProps {
cardName: string;
}
const CityInfo = ({ cardName }: CityInfoProps) => {
const city = cardName.split(" ")[1];
const { data, error, isError } = useQuery<string, Error>({
queryKey: ["cityInfo", city],
queryFn: () => getPlaceInformation(WIKIPEDIA_SEARCH_WORD[city]), // 👈 API 함수
staleTime: 60 * 60 * 1000, // 1시간 동안 fresh 상태로 유지
gcTime: 2 * 60 * 60 * 1000, // 2시간 동안 캐시 유지 (garbage collection 대상 제외)
refetchOnWindowFocus: false, // 윈도우 포커스 시 자동 refetch 비활성화
refetchInterval: 10 * 60 * 1000, // 10분마다 자동 refetch (배경 refetch 포함)
});
// 받아온 HTML 문자열을 XSS 방지를 위해 정화
const cleanHTML = DOMPurify.sanitize(data!);
return (
<div className="text-[14px] overflow-y-auto scrollbar-custom pr-[10px]">
{/* 정제된 HTML을 dangerouslySetInnerHTML을 통해 렌더링 */}
<div dangerouslySetInnerHTML={{ __html: cleanHTML }}></div>
</div>
);
};
try...catch문에 throw error를 안 쓰면?throw error의 역할catch (error) {
// 에러 처리 코드
throw error; // 에러를 다시 바깥으로 전달(전파)
}throw error;는 현재 함수 바깥으로 에러를 전달함getPlaceInformation()을 호출한 쪽에서도 try...catch로 이 에러를 감지할 수 있음throw error를 안 쓰면 어떻게 되나?catch (error) {
// 에러를 콘솔에만 출력하고 끝
console.error("에러 발생", error);
// throw error;
}undefined 반환, 이후 코드가 정상 실행된다고 착각)가 생길 수 있음예시 비교
getPlaceInformation()에서 throw error가 있는 경우
코드
export const getPlaceInformation = async (placeName: string) => {
try {
// 에러가 발생할 가능성이 있는 코드 (정상 동작 시도)
} catch (error) {
// 에러가 실제로 발생했을 때 실행할 코드 (에러 처리)
throw error; // 무조건 던짐
}
};
결과
| 항목 | 값 |
|---|---|
data | undefined |
error | Error 객체 (예: AxiosError) |
isError | true |
설명
throw 되어서 useQuery는 실패로 인식함data는 설정되지 않음error.message에 에러 메시지 있음getPlaceInformation()에서 throw error가 없는 경우 (모든 에러 fallback 처리)
코드
export const getPlaceInformation = async (placeName: string) => {
try {
// 에러가 발생할 가능성이 있는 코드 (정상 동작 시도)
} catch (error) {
// 에러가 실제로 발생했을 때 실행할 코드 (에러 처리)
return "설명 없음"; // fallback 처리
}
};
결과
| 항목 | 값 |
|---|---|
data | "설명 없음" |
error | undefined |
isError | false |
설명
throw 하지 않고 fallback 값을 리턴함useQuery 입장에서는 성공으로 간주함결론
| 상황 | throw error 필요 여부 |
|---|---|
| 호출한 쪽에서 에러를 처리하고 싶다 | 필요함 |
| 함수 내부에서 에러를 완전히 처리했다 (예: fallback 리턴) | 없어도 됨 |
요약
throw error는 에러를 바깥으로 전파함throw를 유지하는 게 좋음return도 하고 throw error도 하면?
return을 한 다음 throw error를 써도 throw는 실행되지 않음return은 그 함수의 실행을 즉시 종료시키기 때문임throw error가 필요 없는 경우함수 안에서 에러가 발생했지만,
➡️ 즉, 내부에서 문제를 감지하고 스스로 처리한 경우
예시 상황: 위키피디아에서 장소 요약을 가져오는데 문서가 없는 경우
export const getPlaceInformation = async (placeName: string) => {
try {
// ... 생략
} catch (error) {
// ✅ 여기서 에러를 완전히 처리
console.warn(`[getPlaceInformation] '${placeName}' 요약을 가져올 수 없습니다.`);
return "설명 없음"; // 👈 fallback 값을 리턴함
}
};
throw error가 필요 없음console.warn()으로 로그 남겼고,반대 예시: fallback 리턴 없이 처리도 안 한 경우
export const getPlaceInformation = async (placeName: string) => {
try {
// ... 생략
} catch (error) {
console.error("에러 발생!");
// 여기서 아무것도 return 하지 않음
// throw error도 하지 않음
// 🚨 호출한 쪽은 에러 발생 사실을 모름
}
};
throw도 안 했으니 호출한 쪽에서는 무슨 일이 일어났는지 모름언제 fallback 리턴하면 좋을까?
fallback이란?원래 보여주려는 콘텐츠가 아직 준비되지 않았거나 문제가 있을 때 임시로 보여주는 콘텐츠
사용 상황
<Suspense><Suspense fallback={<div>로딩 중...</div>}>
<MyComponent />
</Suspense> ➡️ MyComponent가 데이터를 받아오는 동안 <div>로딩 중...</div>이 대신 보여짐ErrorBoundary 혹은 notFound, loading.tsx 등에서<ErrorBoundary fallback={<div>에러 발생!</div>}>
<MyComponent />
</ErrorBoundary> ➡️ 내부에서 오류가 나면 fallback 컴포넌트가 화면에 나타남실제 상황 예시 (Suspense + React Query)
import { Suspense } from "react";
import { useQuery } from "@tanstack/react-query";
const UserInfo = () => {
const { data } = useSuspenseQuery({
queryKey: ["user"],
queryFn: fetchUserData.
});
return <div>{data.name}님 환영합니다!</div>;
};
const App = () => {
<Suspense fallback={<div>사용자 정보를 불러오는 중입니다...</div>}>
<UserInfo />
</Suspense>
};
UserInfo는 서버에서 데이터를 받아올 때까지 시간이 걸림useSuspenseQuery는 로딩/에러 상태를 반환하지 않고, 항상 성공적으로 로드된 데이터만 제공함<Suspense>의 fallback이, 에러 발생 시에는 <ErrorBoundary>의 fallback이 자동으로 보여짐fallback={<div>...}에 있는 "로딩 중입니다" 문구가 먼저 보임UserInfo)가 보임fallback은 왜 필요한가?
| 이유 | 설명 |
|---|---|
| 사용자 경험 향상 | 아무것도 안 보이는 상태보다 "로딩 중..."이라도 보여주는 게 훨씬 친절함 |
| 에러 대비 | 문제 생겨도 완전 흰 화면 대신 안내 메시지나 대체 콘텐츠 제공 |
| 로딩 시간 숨김 | 페이지가 버벅이거나 렌더링 지연될 때, 기다리는 느낌을 줄여줌 |
catch (error) {
if (axios.isAxiosError(error)) {
if (error.response?.status === 404) {
// 케이스 1
return "설명 없음";
} else {
// 케이스 2
console.error("Axios 에러");
}
} else {
// 케이스 3
console.error("일반 에러");
}
}
AxiosError + 404 Not Foundif (axios.isAxiosError(error)) {
if (error.response?.status === 404) {
console.warn(
`[getPlaceInformation] ${placeName}에 대한 위키피디아 문서 없음`
);
return "설명 없음";
}
}throw 안 함AxiosError + 그 외 오류 (500, 403 등)} else {
console.error("[getPlaceInformation] Axios 에러: ", error.message);
}throw 하는 게 좋음else {
console.error("[getPlaceInformation] 일반 에러: ", error);
}throw해서 상위에서 잡는 게 일반적임error instanceof AxiosError
import { AxiosError } from "axios";
if (error instanceof AxiosError) {
// Axios에서 발생한 오류 처리
}
작동 방식: instanceof는 JavaScript의 프로토타입 체인을 따라 error가 AxiosError 클래스의 인스턴스인지 확인함
문제점
AxiosError는 Axios 패키지 내부에서만 정의된 클래스이므로, 버전이나 빌드 환경에 따라 instanceof가 작동하지 않을 수 있음error 객체가 JSON.stringify로 직렬화됐다가 다시 복원된 경우axios.isAxiosError(error)
import axios from "axios";
if (axios.isAxiosError(error)) {
// Axios 오류 처리
}
작동 방식
error.isAxiosError === true를 체크함isAxiosError: true가 붙어 있기 때문에 이 메서드로 확실히 식별할 수 있음장점
error를 AxiosError로 좁혀줌error instanceof AxiosError는 안 되는 이유
axios.isAxiosError(error)를 사용하라고 권장함axios.isAxiosError()가 더 좋은 이유
instanceof는 다른 버전의 Axios가 사용되면 실패할 수 있지만, isAxiosError는 상관없음axios.isAxiosError()를 권장함axios.isAxiosError()가 error 타입을 AxiosError로 좁혀주기 때문에 코드 작성 시 더욱 안전하고 자동 완성이 잘 됨전체 콘솔 메서드 비교표
| 메서드 | 용도 | 콘솔 표시 색상 | 사용 예시 | 표시 필터 |
|---|---|---|---|---|
console.log() | 일반 로그 | 기본(흰색) | 디버깅, 값 확인 | "All", "Logs" |
console.info() | 정보 메시지 | ℹ️ 아이콘 (색상은 브라우저마다 다름) | 앱 상태, API 응답 등 | "Info" |
console.warn() | 경고 | 노란색 ⚠️ | 잠재적 문제 | "Warnings" |
console.error() | 오류 | 빨간색 ❌ | 예외, 실패, 에러 | "Errors" |
console.debug() | 디버그 전용 | 회색 또는 숨겨짐 | 상세 디버깅 정보 | "Verbose" (숨김일 수 있음) |
console.trace() | 호출 스택 출력 | 기본 + 스택트레이스 | 함수 호출 경로 추적 | "Logs" 또는 "Trace" |
각 메서드 상세 설명
console.log()
console.log("현재 위치: ", location.pathname);
console.info()
log()와 기능상 차이는 거의 없음console.info("API 요청 완료. 처리 중...");
console.warn()
console.warn("필수 입력값이 비어있습니다.");
console.error()
console.error("서버 응답 오류:", err);
console.debug()
console.debug("useEffect 내부 상태 변화: ", pathname);

console.trace()
function testTrace() {
console.trace("이 함수는 어디서 호출됐을까?");
}
testTrace();