Skip to content

Conversation

@immhyemi
Copy link
Contributor

@immhyemi immhyemi commented Dec 18, 2025

📝 미션 번호

10주차 Misson 1,2

📋 구현 사항

-영화사이트 랜더링 최적화
-vercel을 활용한 배포

📎 스크린샷

2025-12-18.7.02.10.1.mov

✅ 체크리스트

  • Merge 하려는 브랜치가 올바르게 설정되어 있나요?
  • 로컬에서 실행했을 때 에러가 발생하지 않나요?
  • 불필요한 주석이 제거되었나요?
  • 코드 스타일이 일관적인가요?

🤔 질문 사항

@immhyemi immhyemi requested a review from woojo230 December 18, 2025 10:13
@immhyemi immhyemi self-assigned this Dec 18, 2025
@github-actions
Copy link

github-actions bot commented Dec 19, 2025

🤖 Gemini 코드리뷰 결과

안녕하세요, 시니어 프론트엔드 개발자로서 제출하신 PR의 모든 변경 내용을 면밀히 검토했습니다.
이번 PR은 여러 학습 예제와 초기 프로젝트 설정 변경 사항을 포함하고 있는 것으로 보입니다. 전반적으로 React와 TypeScript의 기본 개념을 잘 이해하고 있으며, 컴포넌트 분리 및 훅 활용 등 현대적인 개발 방식을 적용하려는 노력이 돋보입니다.

아래에서는 중복 피드백을 병합하고 파일별로 정리하여, 코드 품질, 성능, 타입 안전성, 접근성 및 유지보수성을 향상시킬 수 있는 구체적인 개선 제안을 드립니다.


종합 의견

  • 긍정적인 부분: React.memo, useCallback, useMemo와 같은 React 최적화 훅을 적절히 활용하고 있으며, 컴포넌트 분리가 잘 되어 있습니다. 변수명, 함수명도 대부분 명확하고 직관적입니다. Tailwind CSS를 활용한 UI 구현도 깔끔합니다.
  • 주요 개선 영역:
    • API 호출 및 에러 처리 일관성: 여러 API 파일에서 반복되는 로직을 추상화하고, 명확한 타입 정의와 에러 처리를 표준화해야 합니다.
    • 인증 상태 중앙 관리: localStorage 직접 접근 대신 전역 상태 관리 솔루션 도입이 필요합니다.
    • 재사용 가능한 컴포넌트/유틸리티 추출: 중복되는 UI 패턴 (이미지 플레이스홀더, 스켈레톤, 버튼 등)과 범용 로직 (getTimeAgo, 데이터 매퍼)을 분리해야 합니다.
    • 접근성 (Accessibility) 강화: 모달, 폼 요소, 이미지 alt 텍스트 등에 대한 ARIA 속성 및 올바른 HTML 구조를 적용해야 합니다.
    • 타입스크립트 활용 극대화: unknown as 사용을 지양하고, string | undefined와 같은 정확한 타입을 명시하며, useState 타입 추론을 활용하는 등 타입 시스템의 이점을 충분히 활용해야 합니다.
    • 성능 최적화: 이미지 처리 방식 (Base64 -> URL), 복잡한 알고리즘 (소수 계산) 개선을 고려해야 합니다.

파일별 상세 리뷰 및 개선 제안

src/main.tsx

  • ReactDOM.createRoot 엘리먼트의 null 체크 또는 non-null assertion 사용:
    document.getElementById('root') as HTMLElement 대신, root 엘리먼트의 존재 여부를 명시적으로 확인하거나 (더 견고), 일반적으로 main.tsx에서는 존재가 보장되므로 non-null assertion (!)을 사용하는 것이 좋습니다.
    // Option 1: Null check (더 견고)
    const rootElement = document.getElementById('root');
    if (!rootElement) {
      throw new Error("Failed to find the root element. Ensure there is a div with ID 'root' in your index.html.");
    }
    ReactDOM.createRoot(rootElement).render(...);
    
    // Option 2: Non-null assertion (간결, 일반적으로 허용)
    ReactDOM.createRoot(document.getElementById('root')!).render(...);
  • React Router 시스템 활성화:
    src/route/router.tsx에서 정의된 router가 현재 사용되지 않습니다. App 컴포넌트를 직접 렌더링하는 대신, react-router-domRouterProvider를 사용하여 실제 라우팅 기능이 동작하도록 합니다. App 컴포넌트는 라우터의 아웃렛(Outlet) 역할을 하거나, 필요시 제거할 수 있습니다.
    // src/main.tsx
    import { StrictMode } from 'react';
    import { createRoot } from 'react-dom/client';
    import { RouterProvider } from 'react-router-dom';
    import './index.css';
    import { router } from './route/router.tsx';
    
    createRoot(document.getElementById('root')!).render(
      <StrictMode>
        <RouterProvider router={router} />
      </StrictMode>,
    );
  • 파일 끝에 개행 문자 추가: \ No newline at end of file로 끝나고 있습니다. 일관된 코드 스타일과 도구 호환성을 위해 모든 코드 파일 끝에 개행 문자를 추가하는 것이 좋습니다.

src/App.tsx

  • Wrapper 역할 재고: App.tsx는 현재 <UseCallbackPage />, <UseMemoPage />, <MoviePage /> 등 특정 페이지 컴포넌트만 렌더링하는 단순한 Wrapper 역할을 하고 있습니다.
  • 개선 제안: 만약 App 컴포넌트에 전역적인 레이아웃, 라우팅 아웃렛, 전역 상태 관리 프로바이더 등의 역할이 없다면, src/main.tsx에서 해당 페이지 컴포넌트를 직접 렌더링하거나, App.tsx를 제거하고 라우터 설정에서 직접 페이지 컴포넌트를 연결하는 것을 고려하여 불필요한 추상화 레이어를 줄일 수 있습니다.

src/api/movie.ts 및 기타 src/apis/*.ts 파일 (API 호출 관련)

  • API 호출 로직 중복 및 일관성 확보:
    • src/apis/*.ts 파일들에서 API_BASE_URL 변수가 반복 정의되고, localStorage.getItem("accessToken")을 통한 토큰 검증, throw new Error("로그인이 필요합니다.") 로직, fetch 응답 처리 (!response.ok, response.json(), 에러 메시지 생성), 네트워크 오류 처리 로직 등이 반복됩니다.
    • src/apis/login.ts에서는 axios를 사용하고 다른 파일에서는 fetch를 사용하는 등 API 클라이언트가 일관되지 않습니다.
    • 개선 제안:
      1. Fetch API 래퍼 함수 도입 또는 Axios 인스턴스 통일: src/utils/apiClient.ts와 같은 파일을 생성하여 인증 토큰 검증, 공통 헤더 설정, 에러 응답 파싱, 네트워크 오류 처리를 하나의 래퍼 함수(예: fetchWrapper)로 추상화하거나, 전역 axios 인스턴스를 설정하여 모든 API 호출에 사용하도록 통일합니다.
      2. API 함수 반환 타입의 명확성: Promise<any> 또는 불분명한 반환 타입을 사용하는 대신, 예상되는 응답 데이터 구조에 맞는 명확한 타입(예: Promise<CreateCommentResponse>)을 정의하고 적용하여 타입 안정성을 높입니다.
      3. 오류 메시지 타입 가드 개선: (res as unknown as { message?: string }).message와 같은 unknown as 사용을 지양하고, 모든 API 응답에 대한 표준화된 ApiResponse<T>ApiErrorResponse 인터페이스를 정의하고, 이를 사용하여 에러를 처리하는 전역 유틸리티 또는 래퍼를 만듭니다.
      4. TMDB_TOKEN 타입 및 런타임 검사: import.meta.env.VITE_TMDB_TOKEN as string;보다는 string | undefined로 명확히 정의하고, tmdbGet 함수 내부에서 토큰이 undefined일 경우 즉시 Error를 throw하여 요청이 토큰 없이 실행되는 것을 방지합니다.
      5. 쿼리 파라미터 안전성 강화: tmdbGet 함수의 query 파라미터에서 undefinednull 값은 건너뛰도록 로직을 개선하여 API 요청이 올바른 형식으로 전송되도록 합니다.
        async function tmdbGet<T>(path: string, query?: Record<string, string | number | boolean | undefined | null>) {
          if (!TMDB_TOKEN) {
            throw new Error('TMDB API 토큰이 설정되지 않아 요청을 보낼 수 없습니다. .env 파일을 확인하세요.');
          }
          const url = new URL(TMDB_BASE_URL + path);
          if (query) {
            Object.entries(query).forEach(([key, value]) => {
              if (value !== undefined && value !== null) { // undefined 및 null 값 제외
                url.searchParams.set(key, String(value));
              }
            });
          }
          // ...
        }
  • 환경 변수 로깅 제거: console.log('TMDB_TOKEN from env:', ...)는 개발 단계에서는 유용하지만, 프로덕션 빌드에서는 민감 정보가 로그에 남을 수 있으므로 제거하거나 if (import.meta.env.DEV)와 같은 조건부 로깅으로 변경합니다.
  • 기본값 상수 분리: searchMoviesgetPopularMovies 함수에서 language, includeAdult의 기본값이 중복됩니다. 상수로 정의하여 재사용성을 높입니다.

src/components 디렉토리

  • TextInput.tsx (useCallbackTextInput 예제)
    • React.memo 적용: TextInput 컴포넌트에도 React.memo를 적용하여 부모 컴포넌트의 불필요한 리렌더링으로 인한 재렌더링을 방지합니다. (export default React.memo(TextInput);)
    • onChange 핸들러 명시적 타입 지정 및 내부 처리: onChange={(e) => onChange(e.target.value)}와 같이 인라인으로 함수를 정의하고 e의 타입을 추론에 맡기기보다는, 컴포넌트 내부에 이벤트 객체 e의 타입을 명시한 별도의 핸들러 함수(handleInputChange)를 정의하는 것이 안전하고 명확합니다.
      // TextInput.tsx
      const TextInput: React.FC<TextInputProps> = React.memo(({ value, onChange, ...rest }) => {
        const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
          onChange(e.target.value);
        };
        return (
          <input
            // ...
            onChange={handleInputChange} // 내부 핸들러 사용
            {...rest}
          />
        );
      });
    • 재사용성을 위한 추가 props 도입: type, placeholder, id, label, isInvalid, errorMessage, className 등 다양한 prop을 도입하여 TextInput 컴포넌트의 유연성과 접근성을 향상시킵니다. React.InputHTMLAttributes<HTMLInputElement>를 확장하여 모든 HTML <input> 속성 전달을 지원하는 것이 가장 좋습니다.
      // interface TextInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
      //   value: string;
      //   onChange: (value: string) => void;
      //   label?: string;
      //   isInvalid?: boolean;
      //   errorMessage?: string;
      // }
      // const TextInput: React.FC<TextInputProps> = ({ label, value, onChange, isInvalid, errorMessage, className, ...rest }) => {
      //   // ... (구현 시 label, isInvalid, errorMessage 처리 및 className 병합 로직 추가)
      // };
  • CountButton.tsx:
    • value prop 사용 명확화: value prop의 기본값(value = 10)이 설정되어 있지만, UseCallbackPage에서 명시적으로 전달되지 않아 항상 기본값 10이 사용됩니다. 의도된 동작이라면 괜찮지만, 재사용성을 위해 필요하다면 부모에서 value={...}를 명시적으로 전달하는 것을 고려합니다.
    • alt 속성 및 aria-label 개선: 이미지 alt 속성을 단순히 movie.title 대신 영화 ${movie.title} 포스터와 같이 구체적으로 작성하고, 클릭 가능한 요소에는 aria-label을 제공하여 스크린 리더 사용자의 이해를 돕습니다.
  • Modal.tsx, SideBar.tsx (Part 1, 삭제된 코드 베이스):
    • 접근성 (Accessibility) 강화: 모달 컴포넌트에 aria-modal, aria-labelledby, aria-describedby 등의 ARIA 속성을 추가하고, 모달이 열렸을 때 포커스를 모달 내부에 가두는(Focus Trap) 로직, Escape 키로 닫는 로직을 추가해야 합니다. 닫기 버튼에는 aria-label을 제공합니다.
  • ImageWithFallback 또는 ImageWithPlaceholder 컴포넌트 도입:
    • LPCard.tsxMovieCard.tsx, MovieModal.tsx에서 이미지 로딩 실패 시 플레이스홀더 이미지로 대체하는 로직이 반복됩니다.
    • 개선 제안: 이를 <ImageWithFallback />과 같은 재사용 가능한 컴포넌트로 추상화하여 일관된 에러 처리 및 플레이스홀더 관리 기능을 제공합니다.
  • 공통 UI 컴포넌트 추출 (인증 관련 버튼 및 구분선, 스켈레톤 로딩):
    • 구글 로그인 버튼, "OR" 구분선, 그리고 여러 페이지에서 반복되는 스켈레톤 로딩 UI (Tailwind CSS의 animate-pulse 등)를 각각 <GoogleSignInButton />, <Separator text="OR" />, <SkeletonLoader /> 또는 특정 콘텐츠용 스켈레톤 컴포넌트 (LPCardSkeleton)로 추출하여 재사용성을 높이고 코드 중복을 제거합니다.
  • Tailwind CSS 클래스 가독성 개선:
    className 속성 내부에 Tailwind CSS 클래스들이 한 줄에 길게 나열되어 있어 가독성이 떨어질 수 있습니다. 클래스를 기능별로 그룹화하여 여러 줄로 나누어 정렬하면 가독성을 크게 향상시킬 수 있습니다.

src/hooks/useLPDetail.ts, src/hooks/useAuthGuard.ts (삭제된 코드 베이스)

  • 인증 상태 중앙 관리: localStorage를 직접 사용하여 accessToken, userName, userId를 읽고 쓰는 작업이 여러 컴포넌트와 훅에 분산되어 있습니다.
  • 개선 제안: React Context API나 Zustand/Jotai 같은 가벼운 전역 상태 관리 라이브러리를 사용하여 인증 상태(accessToken, userName, userId, isLoggedIn 등)를 중앙에서 관리하는 AuthContext 또는 useAuth 커스텀 훅을 구현하여 코드 중복을 줄이고, 상태 동기화 문제를 해결하며, 테스트 용이성을 높입니다.

src/pages 디렉토리

  • LPCreate/index.tsx (삭제된 코드 베이스):
    • 이미지 업로드 전략 개선: 썸네일 이미지를 FileReader를 통해 Base64 Data URL로 변환하여 폼 데이터에 포함하는 것은 이미지가 커질 경우 네트워크 페이로드를 매우 크게 만듭니다.
    • 개선 제안: 이미지를 서버에 직접 업로드하고 (예: AWS S3, Cloudinary 등) 이미지 URL만 서버로 전송하는 방식으로 변경하여 네트워크 페이로드 크기를 줄이고, 백엔드에서 이미지 최적화나 CDN 연동 등의 처리를 용이하게 합니다.
  • LPDetailPage/index.tsx (삭제된 코드 베이스):
    • LPDetailPageheartlikeCount 상태 관리 로직과 같이 복잡하거나 비즈니스 규칙이 적용된 부분에는 주석을 추가하여 코드 이해와 유지보수성을 높여야 합니다.
    • window.dispatchEvent를 사용하여 "open-delete-account-modal" 이벤트를 발행하는 방식은 React 컴포넌트 간 통신에서 권장되지 않습니다. 대신 컨텍스트 API, 전역 상태 관리 라이브러리, 또는 콜백 함수를 prop으로 전달하는 방식을 사용해야 합니다.
  • MoviePage.tsx:
    • 데이터 로딩 및 에러 상태 관리: API 호출 시 isLoadingisError 상태를 추가하여 사용자에게 현재 앱의 상태에 대한 명확한 시각적 피드백을 제공합니다.
    • mapTmdbToMovies 유틸리티 함수 분리: 컴포넌트 내부에 정의된 mapTmdbToMovies 데이터 변환 로직을 src/utils/movieMapper.ts와 같은 별도의 유틸리티 파일로 분리하고, useCallback으로 감싸 메모이제이션하여 컴포넌트의 책임과 재사용성을 높입니다.
    • React Query 또는 SWR 도입 고려: 현재의 데이터 패칭은 간단하지만, 애플리케이션 규모가 커지고 데이터 캐싱, 백그라운드 재검색, 무한 스크롤 등이 필요해지면 React Query (TanStack Query) 또는 SWR와 같은 비동기 데이터 fetching 라이브러리 도입을 고려하여 개발 편의성 및 성능 최적화를 향상시킬 수 있습니다.
  • UseMemoPage.tsx:
    • getPrimesUpTo 함수 알고리즘 최적화: 현재 소수를 찾는 getPrimesUpTo 함수는 O(n * sqrt(n))의 시간 복잡도를 가집니다. target 값이 수천 이상으로 커질 수 있다면, **에라토스테네스의 체(Sieve of Eratosthenes)**와 같은 O(n log log n) 시간 복잡도의 더 효율적인 알고리즘으로 대체하는 것을 고려합니다.
    • 숫자 입력 처리 및 사용자 피드백 개선: Number(e.target.value) || 0 방식은 유효하지 않은 입력을 0으로 자동 변환하여 사용자가 입력 오류를 인지하기 어렵습니다. rawTargetInput 상태를 도입하여 원본 문자열 입력값을 관리하고, 유효하지 않은 입력 시 시각적인 에러 메시지를 제공합니다.
    • console.log 제거: useMemo 내부의 console.log('소수 다시 계산!')는 개발 단계에서는 유용하지만, 프로덕션 배포 시에는 제거하거나 조건부로 활성화해야 합니다.
    • target의 초기값 UX 고려: target의 초기값이 0인 경우 "소수가 없습니다."라고 표시됩니다. 사용자가 처음 페이지에 들어왔을 때 2부터 시작하여 [2]를 보여주는 등 더 친절한 안내를 고려할 수 있습니다.
  • UseCallbackPage.tsx:
    • useState 타입 추론 활용: useState<number>(10)useState<string>('')와 같이 타입을 명시하고 있지만, TypeScript가 초기값을 기반으로 타입을 효과적으로 추론할 수 있으므로, 타입을 생략하여 코드를 더 간결하게 만들 수 있습니다. (팀의 컨벤션에 따라 선택 가능)

src/types/movie.ts

  • Movie 타입의 posterUrl: posterUrl이 항상 string으로 정의되어 있으며, mapTmdbToMovies에서 항상 대체 이미지 URL을 할당하고 있는 것은 UI 모델을 깔끔하게 유지하는 좋은 접근입니다.

src/route/router.tsx

  • createBrowserRouter로 정의된 router는 현재 src/main.tsx에서 사용되지 않고 있습니다. main.tsx 부분을 참조하여 RouterProvider를 사용하도록 수정합니다.

src/utils 디렉토리 (또는 신규 생성 제안)

  • getTimeAgo 유틸리티 함수 분리: LPDetailPage/components/Comments.tsxLPDetailPage/index.tsx에 중복 정의된 getTimeAgo 함수를 src/utils/date.ts와 같은 별도의 유틸리티 파일로 분리하고 필요한 곳에서 import하여 사용합니다.
  • getPrimesUpTo 유틸리티 함수 분리: UseMemoPage.tsx 내부에 정의된 getPrimesUpTo 함수와 같은 순수 계산 함수는 src/utils/primeUtils.ts와 같은 별도의 파일로 분리하여 재사용성과 가독성을 높입니다.

vite.config.ts

  • rolldown-vite 사용 명시: package.json에서 viterolldown-vite로 alias하여 사용하는 것은 일반적인 설정이 아닙니다. 이것이 빌드 성능 최적화와 같은 의도적인 선택일 가능성이 높으므로, vite.config.ts 파일 내에 주석으로 간략하게 해당 사용 이유를 명시하여 코드의 배경과 의도를 명확히 하는 것이 좋습니다.
    // vite.config.ts
    // package.json에서 'vite'가 'rolldown-vite'로 alias되어 사용되고 있습니다.
    // 이는 빌드 성능 최적화를 위한 의도적인 설정으로 판단됩니다.
    export default defineConfig({
      plugins: [react()],
    });

결론

제시된 코드는 React와 TypeScript의 기본을 잘 따르고 있으나, 위에서 언급된 부분들을 개선함으로써 애플리케이션의 성능, 유지보수성, 타입 안정성, 사용자 경험, 그리고 접근성을 크게 향상시킬 수 있습니다. 특히 API 로직의 중앙화, 전역 상태 관리, 그리고 재사용 가능한 유틸리티/컴포넌트 추출은 프로젝트가 성장함에 따라 코드의 견고함을 유지하는 데 필수적입니다.

이 리뷰가 코드 품질 향상에 실질적인 도움이 되기를 바랍니다. 궁금한 점이 있으시면 언제든지 질문해주세요.

Copy link
Collaborator

@woojo230 woojo230 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

환경변수 파일은 올리지 않도록 합시다

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants