티스토리 뷰
나는 영화 정보를 확인할 수 있는 간단한 프로젝트를 했었다.
이 프로젝트는 자바스크립트 기반의 React 프로젝트로 구성되었다. 강의나 실습으로 배운 타입스크립트 지식을 활용해 자바스크립트로 구성된 프로젝트를 타입스크립트로 전환하기로 했다.
설치
npm install --save typescript @types/react @types/react-dom
`tsconfig.json` 파일 생성을 진행한다.
npx tsc --init
기본 설정
// tsconfig.json
{
"compilerOptions": {
"target": "es2016", // 자바스크립트 코드 버전 변환 설정
"lib": ["DOM", "DOM.Iterable", "ESNext"], // 프로젝트에서 사용할 라이브러리 설정
"jsx": "react-jsx", // JSX 코드 변환 방식 설정
"module": "CommonJS", // 모듈 설정
"moduleResolution": "Node", // 모듈 해석 방식 설정
"allowJs": true, // 자바스크립트 파일을 타입스크립트 프로젝트에 허용할 지 설정
"noFallthroughCasesInSwitch": true, // Switch 문에 case가 누락되었는지 확인하는 옵션
"resolveJsonModule": true, // JSON 모듈을 Import 할 수 있도록 설정
"noEmit": true, // 파일을 출력하지 않도록 설정
"isolatedModules": true, // 각 파일을 독립적으로 트랜스파일
"allowSyntheticDefaultImports": true, // 기본값이 없는 모듈에서도 기본값을 가져올 수 있도록 설정
"esModuleInterop": true, // ES 모듈과 CommonJS 모듈 간 상호 운용성 지원 설정
"forceConsistentCasingInFileNames": true, // 파일명 대소문자 일관성 강제 설정
"strict": true, // 모든 엄격 모드 옵션 활성화(타입 검사를 엄격하게)
"skipLibCheck": true // 라이브러리 파일의 타입 검사 건너뛰기
},
"include": ["./src/**/*"], // 포함할 파일 및 디렉토리 지정
"exclude": ["node_modules", "build", "dist"] // 제외할 파일 및 디렉토리 지정
}
`tsconfig.json`의 기본 설정은 구글 검색과 ChatGPT를 활용해서 작성했다.
자바스크립트 코드를 타입스크립트 코드로 바꾸기
설치와 설정을 마치고 나면, 이제 자바스크립트로 작성되어 있는 코드를 타입스크립트로 전환해야 한다.
기존 파일 확장자를 `.js`, `.jsx`에서 `.ts`, `.tsx`로 바꾸고 나면 에러가 발생할 것이다. 왜냐하면 아직 타입이 주어지지 않았으니까.
간단한 프로젝트였기 때문에 타입 설정이 간단할 줄 알았다. 그러나, 생각했던 것보다 시간이 많이 소요됐다. 연결되어 있는 파라미터의 인자도 같이 맞춰야 했고, 라이브러리에 맞게 타입을 설정해줘야 하는 등 생각보다 고려해야 할 것들이 많았다.
예를 들면, 영화 리스트를 가져오는 API Fetch 함수의 경우
const QueryMovie = async (apiUrl) => {
try {
const response = await axios.get(apiUrl);
return response.data.results;
} catch (error) {
console.error(`에러 발생: ${error}`);
if (error.response) {
const status = error.response.status;
if (status === 404 || status === 500) {
throw {
status,
message: error.response.statusText,
errorMessage: error.response.data?.message || 'No Error Message'
};
}
}
return [];
}
}
자바스크립트로 잓어할 경우, 위 처럼 작성해 사용했었다. `try/catch`문으로 데이터를 가져오고, 에러가 발생할 경우 해당 에러의 상태에 따라 상태, 메시지, 에러 메시지를 전달하도록 했다.
const QueryMovie = async (
apiUrl: string
): Promise<MovieCarouselItemProps[]> => {
try {
const response = await axios.get(apiUrl);
return response.data.results;
} catch (error) {
console.error(`에러 발생: ${error}`);
if (axios.isAxiosError(error)) {
const status = error.response?.status;
if (status === 404 || status === 500) {
throw {
status,
message: error.response?.statusText || "No Error Status Message",
errorMessage: error.response?.data?.message || "No Error Message",
};
}
}
return [];
}
};
이를, 타입스크립트로 전환하고 나서 코드를 위 처럼 수정하였다. 무엇이 달라졌냐 하면, 파라미터의 타입과 `try/catch`문에서 데이터를 가져올 때, 이 데이터들을 어떤 타입으로 반환할 것이냐가 추가되었다.
반환할 데이터가 하나가 아닌 리스트로 여러 개이기 때문에 `MovieCarouselItemProps` 타입을 배열로 설정하였다.
그리고 위의 데이터를 가져오는 함수를 `useQuery`를 활용해 사용했는데, `useQuery` 함수를 타입스크립트로 코드를 바꾸는 과정에서 많은 시간이 소요됐다.
export const useMovieQuery = (key, queryFn, enabled = true) => {
return useQuery({
queryKey: key,
queryFn: queryFn,
enabled: enabled,
staleTime: 10 * 60 * 1000,
cacheTime: 30 * 60 * 1000,
})
}
export const useCarouselQuery = (url) => {
return useMovieQuery(
['carouselResults', url],
() => QueryMovie(url),
!!url
);
};
기존에는 `useQuery`를 반환하는 함수와 영화 리스트 데이터를 가져오도록 하는 `useCarouselQuery` 함수를 생성했다. 자바스크립트를 사용하는 경우에는 특별하게 어렵거나 문제가 되는 부분이 없었다.
type UseMovieQueryOptions<T> = {
key: unknown[];
queryFn: QueryFunction<T>;
enabled?: boolean;
};
export const useMovieQuery = <T>({
key,
queryFn,
enabled = true,
}: UseMovieQueryOptions<T>) => {
return useQuery<T>({
queryKey: key,
queryFn: queryFn,
enabled: enabled,
});
};
타입스크립트로 전환할 때, 생각보다 오류가 여러 번 발생했다. 예를 들어, `staleTime`과 `cacheTime`을 찾을 수 없다는 에러가 있었다. 나중에 조금씩 수정하면서 `staleTime`을 사용할 수 있었지만, 여전히 `cacheTime`은 주어진 타입에서는 찾을 수 없어 사용할 수 없다는 에러가 떴다. 그래서 ChatGPT의 도움을 받아 코드를 조금 수정했다.
type UseMovieQueryOptions<T> = {
key: unknown[];
queryFn: QueryFunction<T>;
enabled?: boolean;
staleTime?: number;
cacheTime?: number;
};
export const useMovieQuery = <T>({
key,
queryFn,
enabled = true,
staleTime = 1000 * 60 * 10,
cacheTime = 1000 * 60 * 30,
}: UseMovieQueryOptions<T>) => {
const options = {
queryKey: key,
queryFn: queryFn,
enabled: enabled,
staleTime: staleTime,
cacheTime: cacheTime,
};
return useQuery<T>(options);
};
`staleTime`과 `cacheTime`에 타입을 주고, 바로 `useQuery`에 조건을 넣어 반환했던 것을 조건을 변수로 선언한 뒤, 이를 담아서 반환하니 제대로 동작하였다.
그렇다면, `staleTime`과 `cacheTime`의 타입을 선언해주지 않았기 때문에 이전 코드에서는 에러가 발생했던 걸까?
이러한 생각으로 타입은 그대로 두고, 이전처럼 `useQuery`에 조건을 바로 넣어 그대로 반환하도록 했는데 이 때는 또 에러가 발생했다. `cacheTime`을 찾을 수 없다는 에러가 발생해 다시 조건을 변수로 두고 반환하였다. (이유를 찾고 공부할 것이다)
그리고, 위 처럼 작성하기는 했지만 `key`의 타입이 `unkown[]`으로 설정하는 게 맞을까? 이 부분에 대해서도 공부가 필요하다고 느꼈다.
그리고 하나 더!
타입스크립트로 코드를 전환한 뒤에 `useCarouselQuery`를 사용하니 로딩 중일 경우에 출력하는 부분에서 수정이 필요했다.
const { status, data, error, isFetching } = useCarouselQuery(RECOMMENDATION_URL);
if (status === 'loading' || isFetching) {
return <MainContentSkeleton />;
}
if (error) {
return <ErrorHandling error={error} viewName="main" />
}
기존에는 `status === 'loading'`으로 처리했었는데, 타입을 설정하고 나니 `loading`을 찾을 수 없다는 에러가 발생했다. 그리고 `loading` 대신 `pending`이 있으니 사용하라는 메시지를 볼 수 있었고, `loading`을 `pending`으로 수정하여 처리했다.
그리고 이 프로젝트에서 스타일을 설정할 때 `Styled-Components`를 사용했는데, 공통적으로 사용되는 스타일에는 `Props`를 전달해 크기를 다르게 설정하도록 했다. 나는 스타일의 `Props`에도 타입을 지정해줘야 하는지 몰랐지만, 이번에 타입스크립트로 전환하면서 `Props`에도 타입을 설정해주어야 하는 것을 알게 됐다.
interface QueryError {
status?: number;
message?: string;
errorMessage?: string;
}
type ErrorProps = {
error: QueryError | null;
viewName: string | null;
};
export const ErrorHandling = ({ error, viewName }: ErrorProps) => {
if (!error) return null;
console.log(error.errorMessage);
switch (error.status) {
case 404:
return <Error404 status={viewName} />;
case 500:
return <Error500 status={viewName} />;
default:
return viewName === "main" ? (
<MainContentSkeleton />
) : viewName === "carousel" ? (
<CarouselSkeleton />
) : (
<Loading />
);
}
};
// ERROR404
export default function Error404({ status }: ErrorProps) {
return (
<ErrorContainer stat={status}>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="size-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z"
/>
</svg>
<ErrorTitle>404 Error</ErrorTitle>
<ErrorMessage>페이지를 찾을 수 없습니다.</ErrorMessage>
</ErrorContainer>
);
}
에러핸들링 역할을 하는 컴포넌트에서 각 뷰포트의 이름에 따라 크기를 다르게 주려고 했다. `mainContent`는 크게, `Carousel`은 작게 말이다. 그래서, `Styled-Components`의 각 에러 스타일에 뷰포트 이름을 `Prop`으로 전달받아 크기를 다르게 주고자 했다.
interface LoadingProps {
stat?: "main" | "carousel" | "error" | string | null;
}
export const ErrorContainer = styled.div<LoadingProps>`
width: 100%;
height: ${(props) =>
props.stat === "main"
? "clamp(300px, 20vw, 450px)"
: props.stat === "carousel"
? "clamp(20vh, 10vw, 40vh)"
: "clamp(350px, 10vw, 500px)"};
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border: 1px solid rgba(0, 0, 0, 0.05);
svg {
width: clamp(50px, 10vw, 150px);
height: clamp(50px, 10vw, 150px);
color: #ff0f0f;
}
`;
위 처럼, `Props`로 전달받는 값 또는 타입을 설정하였다. 그리고 그 값에 따라 높이값을 다르게 주려고 했다.
삼항연산자를 위 처럼 연속으로 사용하는 것은 가독성 문제 등으로 좋지 않다고 배웠는데, 아직 더 나은 방법을 찾지 못했다.
어이없는 실수
자바스크립트를 타입스크립트로 전환하면서 어이없는 일로 시간이 조금 많이 소요된 적이 있었다.
위의 `ErrorHandling` 컴포넌트를 타입스크립트로 전환하는 과정에서 타입을 설정을 해주었는데도 에러가 발생했다.
'Error404' refers to a value, but is being used as a type here. Did you mean 'typeof Error404'?
도대체 이 에러가 무엇일까? `typeof`냐고 왜 물어볼까?
ChatGPT에 물어봐도 제대로 된 답을 주지 못해서 에러 메시지를 여러 번 읽고 해석하고 타입을 바꿔보고 해볼 수 있는 건 다 해봤다. 그래도 위의 에러가 사라지지 않았다.
그래서 마지막으로 구글 검색을 해봤더니 정말 단순한 문제였다. 바로 확장자 문제!
`ErrorHandling` 컴포넌트를 `.ts`으로 변경한 것이 문제였다. `.tsx`로 변경해야 하는 것을 `.ts`로 변경해서 위와 같은 에러가 발생한 것이었다. 그래서 `ErrorHandling` 컴포넌트를 제대로 읽지 못한 것이었다.
`Switch` 문으로 반환 처리하면서 반환하는 것이 값을 내보내는 것으로 착각하여 이러한 문제가 만들게 됐다.
이번 경험으로 같은 실수가 반복되지 않도록 더 집중해야 겠다고 느꼈다.
'프론트엔드 > TypeScript' 카테고리의 다른 글
기술 지식 2단계 내용 정리 (1) | 2024.08.16 |
---|---|
리터럴(literal) 타입 활용 방법을 예시를 통해 배우기 (0) | 2023.05.29 |
- Total
- Today
- Yesterday
- 원티드 프리온보딩 챌린지
- javascript
- LottieFiles
- Singleton
- PostechAppleDeveloperAcademy
- Default Branch
- 포스텍애플아카데미
- 프론트엔드 챌린지
- 포스텍애플디벨로퍼아카데미
- #포스텍애플디벨로퍼아카데미
- 신입개발자가 준비해야 할 것들
- Express
- 최종추가합격
- React
- 코딩테스트 대비
- 싱글톤
- 스프링
- if(kakao)dev2022
- DB Error MongooseServerSelectionError
- 개발자이력서꿀팁
- 고민한 부분
- 자바스크립트
- Frontend
- 깃허브 Merge
- 그룹인터뷰후기
- 원티드 프리온보딩
- 조코딩과함께
- 설명회느낌점
- node
- 개발 이력서 지원 팁
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |