Skip to content

[6주차] Team 인플루이 최서연 & 한서정 미션 제출합니다. #16

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 76 commits into
base: master
Choose a base branch
from

Conversation

seoyeon5117
Copy link

배포 링크

🔗 배포 링크

Key Questions

1. 정적 라우팅(Static Routing)/동적 라우팅(Dynamic Routing)이란?

라우팅 (Routing) 이란?

정적 라우팅(Static Routing)

정해진 URL에 대해 정적으로 페이지나 컴포넌트를 매핑해 두는 방식

→ 라우터가 미리 정의된 경로만 인식하며 URL이 고정되어 있다.

예시

  • <Route path="/about" element={<About />} />
    https://~~/about 으로 들어가면 접근 가능

특징

  • 라우팅 경로가 미리 코드에 명시되어 있음
  • 경로와 컴포넌트가 1:1로 명확하게 연결됨

장점

  • 구현 간단, 예측 가능
  • SEO에 유리

단점

  • 유동적인 데이터 기반 페이지에는 적합하지 않다.

동적 라우팅 (Dynamic Routing)

URL의 일부가 변수처럼 동적으로 바뀌고, 그에 따라 해당 데이터를 불러오거나 컴포넌트를 렌더링하는 방식이다.

예시

  • 리액트: <Route path="/user/:userId" element={<UserProfile />} />
    • /user/123
  • Next.js: 폴더 경로를 /pages/post/[id].tsx

장점

  • 유연함: 수많은 페이지를 하나의 컴포넌트로 커버 가능
  • RESTful URL 설계 가능 (e.g., /products/:productId)

단점

  • 구현이 정적 라우팅보다 복잡할 수 있음
  • SEO 설정이 어렵거나 추가 설정 필요 (특히 클라이언트 렌더링 기반일 때)
  • 잘못된 URL 입력 시 에러 처리 필요

2. 무한 스크롤과 Intersection Observer API의 특징에 대해 알아봅시다.

무한 스크롤

화면을 아래로 스크롤할 때 콘텐츠가 끊기지 않고 계속해서 로드되는 페이지 목록 디자인으로, 페이지의 최하단에 도달했을 때 신규 콘텐츠를 로드하는 식으로 동작.

장점

  • 콘텐츠 탐색이 쉬움(별도의 버튼을 누르지 않아도 계속해서 콘텐츠를 볼 수 있음)
  • 모바일 기기에 적합

단점

  • 특정 콘텐츠를 다시 찾기 어려움
  • footer에 접근할 수 없음
  • 콘텐츠가 방대할수록 페이지 속도가 느려짐

무한 스크롤 혹은 특정 요소를 관찰하고 싶을 때는 Intersection observer API 사용을 권장.

Intersection Obeserver API

Intersection Observer(교차 관찰자)는 Web API 중 하나로, 기본적으로 관찰 중인 요소가 뷰포트와 교차하고 있는지 감지하는 API. 관찰 중인 요소가 사용자가 보는 화면 영역 내에 들어왔는지 알려주는 API.

특정 요소가 다른 요소와의 교차점에 들어가거나 나갈 때 두 요소 간의 교차점이 지정된 양만큼 변화될 때 실행되는 콜백 함수를 코드에 등록할 수 있다.

대상 요소가 기기의 뷰포트 또는 특정 요소(루트 요소/루트)와 교차하거나, observer가 최초로 대상 요소를 관찰하라고 요청을 받은 시점에 사용자가 제공한 콜백 함수를 실행한다. 대상 요소와 루트 사이의 교차 정도는 intersection ratio로, 0.0과 1.0 사이의 값으로 보이는 대상 요소를 백분율로 나타낸다.

  1. 교차 관찰자 생성

Intersection observer는 생성자를 호출하고, threshold가 한 방향 혹은 다른 방향으로 교차할 때마다 실행하기 위한 callback 함수를 전달하여 생성한다.

let options = {
  root: document.querySelector("#scrollArea"),
  rootMargin: "0px",
  threshold: 1.0, // root 옵션으로 지정된 요소 내에서 타겟 요소가 100% 보이면 콜백이 호출된다는 의미.
};

let observer = new IntersectionObserver(callback, options);

root: 대상 가시성을 체크하기 위한 뷰포트로 사용되는 요소로, 반드시 타겟의 상위 요소여야 함. 뷰포트를 지정하지 않거나 null이면 브라우저 뷰포트가 기본으로 설정됨.

rootMargin: 루트 주위의 여백. 기본값은 0.

threshold: 관찰자의 콜백이 무조건 실행되어야 하는 대상의 가시성 백분율을 나타내는 숫자 또는 숫자 배열. 기본값은 0으로, 1픽셀이라도 보이면 콜백 실행.

  1. 관찰할 대상 요소 전달

관찰자 생성 후에는 관찰할 타겟 요소를 전달해야 한다.

let target = document.querySelector("#listItem");
observer.observe(target);

// observer를 위해 설정한 콜백은 바로 지금 최초로 실행됩니다
// 대상을 관찰자에 할당할 때까지 기다립니다

언제든지 타겟 요소가 IntersectionObserver에 지정된 임계값을 만족시키면 콜백이 호출된다. 콜백은 IntersectionObserverEntry 객체와 관찰자 목록을 받음.

let callback = (entries, observer) => {
  entries.forEach((entry) => {
    // 각 엔트리는 관찰된 하나의 교차 변화을 설명합니다.
    // 대상 요소:
    //   entry.boundingClientRect
    //   entry.intersectionRatio
    //   entry.intersectionRect
    //   entry.isIntersecting
    //   entry.rootBounds
    //   entry.target
    //   entry.time
  });
};
  1. 교차 변화 콜백

루트 요소 내에 보이는 대상 요소의 양이 임계값 가시성을 지날 때, IntersectionOnbserver 객체의 콜백이 실행된다. 콜백은 모든 교차된 각 임계값 중 하나씩, IntersectionObserverEntry 객체의 배열과 IntersectionObserver 객체 자체에 대한 참조를 입력을 받음.

이벤트 처리기 + Element.getBoundingClientRect()와 같은 메서드로도 무한 스크롤을 처리할 수 있다. 하지만 스크롤을 진행하는 순간마다 이벤트가 호출되어 메인 스레드 성능이 좋지 않고, 쓰로틀과 같은 최적화 작업이 동반되어야 한다. 특정 지점을 관찰하는 getBoundingClientRect() 또한 계산을 할 때마다 reflow 현상(문서 내 요소의 위치와 도형을 다시 계산하여 문서의 일부 또는 전체를 다시 렌더링)이 일어난다. 스크롤 이벤트와 달리 Intersection Observer는 메인 스레드와 별개로 비동기적으로 실행되므로 별도의 최적화가 없어도 성능이 좋다.

3. tanstack query의 사용 이유(기존의 상태 관리 라이브러리와는 어떻게 다른지)와 사용 방법(reactJS와 nextJS에서)을 알아봅시다.

TanStack Query

서버로부터 데이터 가져오기, 데이터 캐싱, 캐시 제어 등 데이터를 쉽고 효율적으로 관리할 수 있는 라이브러리.

데이터 가져오기 및 캐싱, 동일 요청의 중복 제거, 신선한 데이터 유지, 무한 스크롤/페이지네이션 등의 성능 최적화, 네트워크 재연결, 요청 실패 등의 자동 갱신이 주 기능이다.

리액트로 개발하는 과정에서 props drilling이나 공통된 데이터를 여러 컴포넌트에서 사용하는 경우가 존재한다. TanStack Query는 API 통신 및 비동기 데이터 관리에 특화된 라이브러리이다.

대부분의 전통적인 상태 관리 라이브러리는 클라이언트 상태를 다루는 데 훌륭하지만, 비동기나 서버 상태를 다루는 데 뛰어나지 않다. 서버 상태는 원격에 저장되며, 이를 제어하거나 소유할 수 없는 경우가 많고, 데이터를 가져오거나 업데이트하기 위해 비동기 API를 필요로 한다. 하지만 TanStack Query는 서버 상태를 관리에 특화되어 있다.

사용 방법

import {
  useQuery,
  useMutation,
  useQueryClient,
  QueryClient,
  QueryClientProvider,
} from '@tanstack/react-query'
import { getTodos, postTodo } from '../my-api'

// Create a client
const queryClient = new QueryClient()

function App() {
  return (
    // Provide the client to your App
    <QueryClientProvider client={queryClient}>
      <Todos />
    </QueryClientProvider>
  )
}

function Todos() {
  // Access the client
  const queryClient = useQueryClient()

  // Queries
  const query = useQuery({ queryKey: ['todos'], queryFn: getTodos })

  // Mutations
  const mutation = useMutation({
    mutationFn: postTodo,
    onSuccess: () => {
      // Invalidate and refetch
      queryClient.invalidateQueries({ queryKey: ['todos'] })
    },
  })

  return (
    <div>
      <ul>{query.data?.map((todo) => <li key={todo.id}>{todo.title}</li>)}</ul>

      <button
        onClick={() => {
          mutation.mutate({
            id: Date.now(),
            title: 'Do Laundry',
          })
        }}
      >
        Add Todo
      </button>
    </div>
  )
}

render(<App />, document.getElementById('root'))

TanStack Query는 위 예시처럼 사용할 수 있다.

TanStack Query는 useQuery라는 훅을 사용하여 서버에 필요한 데이터를 비동기적으로 요청한다. 클라이언트 렌더링 때 데이터를 호출하고 관리하는 라이브러리이기 때문에 웹 서버에 제일 처음 접속하여 수신 받는 HTML 문서에는 API 호출 이후에 렌더링 되는 데이터들이 존재하지 않아 초기 로딩 속도 및 엔진 최적화에서 바람직하지 않다.

Next.js 환경에서 SSR을 구현하기 위해서는 getStaticProps 함수 혹은 getServerSideProps를 사용할 수 있다.

export const getServerSideProps = () => {
  const data = fetch(api).then((r) => r.json()).then((r) => r);

  return {
    props: {
      data,
    },
  };
};

위 코드를 사용하면 api라는 엔드포인트에 데이터를 호출하여 SSR에서 데이터를 가져온 뒤, props 객체에 데이터를 담아 페이지를 렌더링할 수 있다.

이 데이터를 TanStack Query에 넣어서 dehydrate로 직렬화하여 클라이언트에 전달하면, 클라이언트는 Hydrate 컴포넌트를 통해 이 직렬화된 데이터를 읽어 다시 fetch하지 않고도 초기 상태로 사용할 수 있다.

export async function getServerSideProps() {
  const queryClient = new QueryClient();
  await queryClient.prefetchQuery(['posts'], fetchPosts);

  return {
    props: {
      dehydratedState: dehydrate(queryClient), // 직렬화된 캐시 데이터 전달
    },
  };
}

seoyeon5117 and others added 27 commits May 10, 2025 16:54
[FEAT] Home 페이지 API 연결
[Feat] search 기능 구현 + 영화 클릭시 연결 + 배포 에어 수정
[FEAT] 404 페이지 & 화면 최대 너비 조정
[FEAT] 검색페이지 및 메인페이지 스켈레톤 UI 적용
[FIX] Search 페이지 오류 해결
</button>
</div>

{/* Movie List */}

Choose a reason for hiding this comment

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

코드가 길어지고, 반복되는 패턴의 UI가 많은 것 같은데 컴포넌트로 분리하는 건 어떨까요??

import { useRouter } from "next/navigation";

const Home = () => {
const HeaderList = [

Choose a reason for hiding this comment

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

현재 headerlist가 home 내부에 있는데 따로 분리해서 작성하면 어떨까요?


import { useRouter } from "next/navigation";

const NotFoundPage = () => {

Choose a reason for hiding this comment

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

not found 페이지 따로 구현해 놓으신 것 좋네요! 참고하겠습니다

Copy link
Author

Choose a reason for hiding this comment

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

not-found.tsx 파일로 작성하면 자동 적용됩니다!

) : top10Error ? (
<div className="absolute inset-0">Error: {top10Error.message}</div>
) : (
<Image

Choose a reason for hiding this comment

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

저는 그냥 img 형식으로 사용했는데 image 컴포넌트 활용하는 게 좋겠네요! 다음 과제부터 적용해 봐야겠습니당

@@ -0,0 +1,8 @@
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

Choose a reason for hiding this comment

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

이런 것도 있군요!! 처음 봤는데 하나 배워갑니다

@only1Ksy
Copy link

이번 주차 과제도 수고 많으셨습니다!☺️

Copy link

@BeanMouse BeanMouse left a comment

Choose a reason for hiding this comment

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

코드 수준이 너무 높은것 같아요...
저도 언젠가는 이렇게 될 수 있겠져? ㅠ,ㅠ

가면 갈 수록 따라 잡기가 힘들다고 느껴지는 하루였습니다

import { Suspense } from "react";
const MovieDetailContent = () => {
const searchParams = useSearchParams();
const movieId = searchParams.get("id");

Choose a reason for hiding this comment

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

무비 ID도 못 받아올 수 있는 경우를 고려하면 어떨까여?

const API_KEY = process.env.NEXT_PUBLIC_TMDB_API_KEY;
const BASE_URL = process.env.NEXT_PUBLIC_TMDB_API_BASE_URL;

const fetchTMDB = async <T>(

Choose a reason for hiding this comment

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

제네릭 타입으로 한 게 인상 깊었습니다 저도 나중에 참고해보겟습니다!

{isLoading &&
page > 1 &&
Array.from({ length: ITEMS_PER_PAGE }).map((_, index) => (
<div key={index} className="animate-skeleton h-19 w-full" />

Choose a reason for hiding this comment

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

index로 key값을 주는게 불안정하다고 하는데 prefix를 줘서 skeleton-${index} 이런식으로 적어보는 건 어떨까여

Copy link
Author

Choose a reason for hiding this comment

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

ㅎㅎ사실 이거 제가 작성했습니다... key에 index를 넣으면 안되는 가장 큰 이유가 배열 데이터가 변경 될 때 성능이 떨어지기 때문이라고 생각하는데 이건 스켈레톤 UI라 배열 데이터가 변경될 일이 없고, 다른 곳에서도 skeleton-${index}와 같은 방식으로 적더라도 같은 문제가 발생한다고 생각해서 그냥 key에 index 값을 주었습니다!


const TvDetailContent = () => {
const searchParams = useSearchParams();
const tvId = searchParams.get("id");

Choose a reason for hiding this comment

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

이것도 마찬가지로 잘못되었을 경우도 넣으면 좋을 것 같습니다!

{/* 플레이 버튼 */}
<button
type="button"
className="h-[2.8125rem] w-[18.9375rem] shrink-0 cursor-not-allowed space-x-4 rounded-[0.35156rem] bg-[#C4C4C4] text-[1.27888rem] leading-[146.61%] font-semibold tracking-[-0.00381rem] text-black"

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.

4 participants