노마드 코더 강의에 따라 넷플릭스 클론 코딩을 해보잣
페이지 이동 처리 - react-router-dom v6 사용
<BrowserRouter>
<Header />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/tv" element={<Tv />} />
<Route path="/search" element={<Search />} />
</Routes>
</BrowserRouter>;
Header ( 헤더 )
styled component로 헤더 구성

기본적인 헤더 디자인을 만들었다.
추후 애니메이션 처리를 위해 로고는 svg 태그로 만들었다.
애니메이션 처리를 하고 싶은 컴포넌트는 모션 컴포넌트로 생성해줘야 한다.
애니메이션 효과 - 로고 색상 변경하기
1. variants를 생성한다.
2. 컴포넌트 props에 생성한 variants를 담는다.
3. 각 속성을 지정한다.
애니메이션 효과 - 메뉴 선택 강조
1. 강조 표시를 주는 Circle 스타일 컴포넌트를 각 메뉴에 추가한다.
<Items>
<Item>
Home
<Circle />
</Item>
<Item>
Tv Shows
<Circle />
</Link>
</Item>
</Items>;
2. react-router-dom의 Link를 사용하여 페이지 이동 기능을 구현한다.
3. useMatch 훅을 사용하여 현재 페이지가 해당 링크와 동일한지를 boolean값으로 받는다.
const homeMatch = useMatch("/");
const tvMatch = useMatch("tv");
4. boolean 값에 따라 Circle 컴포넌트가 마운트되도록 한다.
5. Circle 컴포넌트에 동일한 layoutId를 부여한다.
<Link to="tv">
Tv Shows
{tvMatch && <Circle />}
</Link>;
애니메이션 효과 - 검색창
방법 1.
1. Input 스타일 컴포넌트를 생성한다.
2. state 값을 만들고 검색 아이콘에 토글 함수를 생성하여 이벤트 리스너 설정을 한다.
3. animate와 transition 속성으로 각각 애니메이션 효과를 준다.
const Input = styled(motion.input)`
transform-origin: right center; /*변형의 시작점을 변경할 수 있다.*/
position: absolute;
left: -150px;
`;
<Search>
<motion.svg
onClick={openSearch}
animate={{ x: searchOpen ? -180 : 0 }}
transition={{ type: "linear" }} /*부드러운 애니메이션 처리를 위해서*/
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="~"
clipRule="evenodd"
></path>
</motion.svg>
<Input
placeholder="검색어를 입력하세요"
transition={{ type: "linear" }}
animate={{ scaleX: searchOpen ? 1 : 0 }}
/>
</Search>;
방법2.
onClick 이벤트 핸들러에서 모든 애니메이션 처리를 하는 것이다.
다량의 애니메이션 효과를 한번에 주고 싶을 때 유용하다.
const inputAnimation = useAnimation();
const toggleSearch = () => {
if (searchOpen) {
inputAnimation.start({
scaleX: 0,
});
} else {
inputAnimation.start({ scaleX: 1 });
}
setSearchOpen((prev) => !prev);
};
<Input animate={inputAnimation} transition={{ type: "linear" }}/>
애니메이션 효과 - 스크롤에 따른 상단바 색상 변경
1. useScroll로 값을 받는다.
const { scrollY } = useScroll();
2. 애니메이션 적용
<Nav
animate={{
backgroundColor:
Number(scrollY) > 80 ? "rgba(0, 0, 0, 0)" : "rgba(0, 0, 0, 1)",
}}
></Nav>;
Home ( 홈 )
데이터 불러오기
상태관리 라이브러리인 react - query를 사용할 것이다.
데이터는 TheMovieDB API로 가져온다.
1. 쿼리 클라이언트를 만들고 쿼리 클라이언트 provider로 전체 컴포넌트를 감싼다.
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const client = new QueryClient();
<QueryClientProvider client={client}>
<ThemeProvider theme={theme}>
<GlobalStyle />
<App />
</ThemeProvider>
</QueryClientProvider>;
2. theMovieDB api의 key를 받는다.
//api.ts
const API_KEY = "c7ed214708d356a99cf12085cc89c05e";
const BASE_PATH = "https://api.themoviedb.org/3";
3. 현재 상영 중인 영화 데이터를 받는 fetch 함수를 만든다.
export function getMovies() {
return fetch(`${BASE_PATH}/movie/now_playing?api_key=${API_KEY}`).then(
(response) => response.json()
);
}
4. 데이터 타입 인터페이스를 만든다. (IGetMoviesResult)
5. useQuery 훅을 사용하여 데이터와 로딩 여부를 받는다.
//Home.tsx
const { data, isLoading } = useQuery<IGetMoviesResult>(
["movies", "nowPlaying"],
getMovies
);
배너 만들기

1. 배너 이미지 경로 생성 함수 만들기
export function makeImagePath(id: string, format?: string) {
return `https://image.tmdb.org/t/p/${format ? format : "original"}/${id}`;
}
2. 배너 컴포넌트 생성하기
const Banner = styled.div<{ bgPhoto: string }>`
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
padding: 60px;
background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 1)),
url(${(prop) => prop.bgPhoto}); // 배경 두개 생성
background-size: cover;
`;
<Banner bgPhoto={makeImagePath(data?.results[0].backdrop_path || "")}>
<h2>{data?.results[0].title}</h2>
<p>{data?.results[0].overview}</p>
</Banner>;
슬라이더 만들기

const rowVariants = {
hidden: {
x: window.outerWidth + 5,
},
visible: {
x: 0,
},
exit: {
x: -window.outerWidth - 5,
},
};
const boxVariants = {
normal: {
scale: 1,
},
hover: {
scale: 1.3,
y: -80,
transition: {
delay: 0.5,
duration: 0.1,
type: "tween",
},
},
};
<Slider>
<AnimatePresence initial={false} onExitComplete={toggleLeaving}>
<Row
variants={rowVariants}
initial="hidden"
animate="visible"
exit="exit"
transition={{ type: "tween" }}
key={idx}
>
{data?.results
.slice(1)
.slice(offset * idx, offset * idx + offset)
.map((movie) => (
<Box
variants={boxVariants}
whileHover="hover"
initial="normal"
transition={{ type: "tween" }}
key={movie.id}
bgPhoto={makeImagePath(movie.backdrop_path, "w500")}
>
<Info variants={infoVariants}>
<h4>{movie.title}</h4>
</Info>
</Box>
))}
</Row>
</AnimatePresence>
</Slider>;
AnmatePresence 컴포넌트는 사라지는 컴포넌트를 애니메이트 해준다.
props 중 initial은 초기값 세팅 여부를 나타내는 것이다.
onExitComplete은 컴포넌트가 사라진 후 콜백되는 함수이다.
상세설명 모달
모달 생성
1. 영화가 클릭될 때 모달을 열기 위해 호출되는 모달 생성 함수를 만든다.
그리고 Box 컴포넌트의 props로 전달한다.
useNavigate 훅을 사용하여 해당 영화의 상세설명 컴포넌트로 url 이동을 한다.
const navigate = useNavigate();
const onBoxClicked = (movieId: number) => {
navigate(`/movies/${movieId}`);
};
<Box
...
onClick={() => onBoxClicked(movie.id)}
></Box>;
2. 모달 컴포넌트(BigMovie)를 생성한다.
position을 fixed 시켜 스크롤의 움직임에 상관없이 모달의 위치를 고정한다.
const BigMovie = styled(motion.div)`
position: fixed;
height: 70vh;
top: 0;
bottom: 0;
left: 0;
right: 0;
`;
3. useMatch 훅을 사용하여 url 매칭을 확인한다.
url매칭이 true를 리턴할 경우 모달 컴포넌트가 렌더링 되도록 한다.
const bigMovieMatch: PathMatch<string> | null = useMatch("/movies/:movieId");
{bigMovieMatch ? (
<BigMovie layoutId={bigMovieMatch.params.movieId} />
) : null}
4. Box 컴포넌트와 모달 컴포넌트에 동일한 layoutId를 주어 transition를 적용한다.
모달 소멸
1. 오버레이 컴포넌트를 생성한다.
position을 fixed 시켜 컴포넌트의 위치를 화면 전체로 고정시킨다.
const Overlay = styled(motion.div)`
position: fixed;
top: 0;
width: 100%;
height: 100%;
background-color: rgb(0, 0, 0, 0.5);
opacity: 0;
`;
2. 오버레이 클릭할 때 home으로 돌아가도록 호출되는 onClick 함수를 생성한다.
오버레이 컴포넌트의 props로 전달한다.
const onOverlayClicked = () => {
navigate(`/`);
};
{bigMovieMatch ? (
<>
<Overlay
onClick={onOverlayClicked}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
<BigMovie layoutId={bigMovieMatch.params.movieId} />
</>
) : null}
모달 채우기
movieId를 사용하여
검색창 기능 구현하기
1. useForm 사용
2. submit 함수 navigate 사용 ?로 키워드 전달
3. useLocation 사용하여 url에서 keyword 확인
4. js의 URLSearchParameter을 사용하여 파싱
5. api에 요청
'Tech > TypeScript' 카테고리의 다른 글
[ TS 기초 ] 함수 - Call signiture / 오버로딩 / 제너릭 (0) | 2023.03.21 |
---|---|
[ TS 기초 ] 타입스크립트란 (0) | 2023.03.21 |
[Typescript / TS] Frame motion을 사용한 애니메이션 (0) | 2023.01.29 |
[Typescript / TS] ToDo 칸반보드 만들기 (0) | 2023.01.23 |
[Typescript / TS] ToDo리스트 만들기 (0) | 2023.01.16 |
노마드 코더 강의에 따라 넷플릭스 클론 코딩을 해보잣
페이지 이동 처리 - react-router-dom v6 사용
<BrowserRouter>
<Header />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/tv" element={<Tv />} />
<Route path="/search" element={<Search />} />
</Routes>
</BrowserRouter>;
Header ( 헤더 )
styled component로 헤더 구성

기본적인 헤더 디자인을 만들었다.
추후 애니메이션 처리를 위해 로고는 svg 태그로 만들었다.
애니메이션 처리를 하고 싶은 컴포넌트는 모션 컴포넌트로 생성해줘야 한다.
애니메이션 효과 - 로고 색상 변경하기
1. variants를 생성한다.
2. 컴포넌트 props에 생성한 variants를 담는다.
3. 각 속성을 지정한다.
애니메이션 효과 - 메뉴 선택 강조
1. 강조 표시를 주는 Circle 스타일 컴포넌트를 각 메뉴에 추가한다.
<Items>
<Item>
Home
<Circle />
</Item>
<Item>
Tv Shows
<Circle />
</Link>
</Item>
</Items>;
2. react-router-dom의 Link를 사용하여 페이지 이동 기능을 구현한다.
3. useMatch 훅을 사용하여 현재 페이지가 해당 링크와 동일한지를 boolean값으로 받는다.
const homeMatch = useMatch("/");
const tvMatch = useMatch("tv");
4. boolean 값에 따라 Circle 컴포넌트가 마운트되도록 한다.
5. Circle 컴포넌트에 동일한 layoutId를 부여한다.
<Link to="tv">
Tv Shows
{tvMatch && <Circle />}
</Link>;
애니메이션 효과 - 검색창
방법 1.
1. Input 스타일 컴포넌트를 생성한다.
2. state 값을 만들고 검색 아이콘에 토글 함수를 생성하여 이벤트 리스너 설정을 한다.
3. animate와 transition 속성으로 각각 애니메이션 효과를 준다.
const Input = styled(motion.input)`
transform-origin: right center; /*변형의 시작점을 변경할 수 있다.*/
position: absolute;
left: -150px;
`;
<Search>
<motion.svg
onClick={openSearch}
animate={{ x: searchOpen ? -180 : 0 }}
transition={{ type: "linear" }} /*부드러운 애니메이션 처리를 위해서*/
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="~"
clipRule="evenodd"
></path>
</motion.svg>
<Input
placeholder="검색어를 입력하세요"
transition={{ type: "linear" }}
animate={{ scaleX: searchOpen ? 1 : 0 }}
/>
</Search>;
방법2.
onClick 이벤트 핸들러에서 모든 애니메이션 처리를 하는 것이다.
다량의 애니메이션 효과를 한번에 주고 싶을 때 유용하다.
const inputAnimation = useAnimation();
const toggleSearch = () => {
if (searchOpen) {
inputAnimation.start({
scaleX: 0,
});
} else {
inputAnimation.start({ scaleX: 1 });
}
setSearchOpen((prev) => !prev);
};
<Input animate={inputAnimation} transition={{ type: "linear" }}/>
애니메이션 효과 - 스크롤에 따른 상단바 색상 변경
1. useScroll로 값을 받는다.
const { scrollY } = useScroll();
2. 애니메이션 적용
<Nav
animate={{
backgroundColor:
Number(scrollY) > 80 ? "rgba(0, 0, 0, 0)" : "rgba(0, 0, 0, 1)",
}}
></Nav>;
Home ( 홈 )
데이터 불러오기
상태관리 라이브러리인 react - query를 사용할 것이다.
데이터는 TheMovieDB API로 가져온다.
1. 쿼리 클라이언트를 만들고 쿼리 클라이언트 provider로 전체 컴포넌트를 감싼다.
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const client = new QueryClient();
<QueryClientProvider client={client}>
<ThemeProvider theme={theme}>
<GlobalStyle />
<App />
</ThemeProvider>
</QueryClientProvider>;
2. theMovieDB api의 key를 받는다.
//api.ts
const API_KEY = "c7ed214708d356a99cf12085cc89c05e";
const BASE_PATH = "https://api.themoviedb.org/3";
3. 현재 상영 중인 영화 데이터를 받는 fetch 함수를 만든다.
export function getMovies() {
return fetch(`${BASE_PATH}/movie/now_playing?api_key=${API_KEY}`).then(
(response) => response.json()
);
}
4. 데이터 타입 인터페이스를 만든다. (IGetMoviesResult)
5. useQuery 훅을 사용하여 데이터와 로딩 여부를 받는다.
//Home.tsx
const { data, isLoading } = useQuery<IGetMoviesResult>(
["movies", "nowPlaying"],
getMovies
);
배너 만들기

1. 배너 이미지 경로 생성 함수 만들기
export function makeImagePath(id: string, format?: string) {
return `https://image.tmdb.org/t/p/${format ? format : "original"}/${id}`;
}
2. 배너 컴포넌트 생성하기
const Banner = styled.div<{ bgPhoto: string }>`
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
padding: 60px;
background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 1)),
url(${(prop) => prop.bgPhoto}); // 배경 두개 생성
background-size: cover;
`;
<Banner bgPhoto={makeImagePath(data?.results[0].backdrop_path || "")}>
<h2>{data?.results[0].title}</h2>
<p>{data?.results[0].overview}</p>
</Banner>;
슬라이더 만들기

const rowVariants = {
hidden: {
x: window.outerWidth + 5,
},
visible: {
x: 0,
},
exit: {
x: -window.outerWidth - 5,
},
};
const boxVariants = {
normal: {
scale: 1,
},
hover: {
scale: 1.3,
y: -80,
transition: {
delay: 0.5,
duration: 0.1,
type: "tween",
},
},
};
<Slider>
<AnimatePresence initial={false} onExitComplete={toggleLeaving}>
<Row
variants={rowVariants}
initial="hidden"
animate="visible"
exit="exit"
transition={{ type: "tween" }}
key={idx}
>
{data?.results
.slice(1)
.slice(offset * idx, offset * idx + offset)
.map((movie) => (
<Box
variants={boxVariants}
whileHover="hover"
initial="normal"
transition={{ type: "tween" }}
key={movie.id}
bgPhoto={makeImagePath(movie.backdrop_path, "w500")}
>
<Info variants={infoVariants}>
<h4>{movie.title}</h4>
</Info>
</Box>
))}
</Row>
</AnimatePresence>
</Slider>;
AnmatePresence 컴포넌트는 사라지는 컴포넌트를 애니메이트 해준다.
props 중 initial은 초기값 세팅 여부를 나타내는 것이다.
onExitComplete은 컴포넌트가 사라진 후 콜백되는 함수이다.
상세설명 모달
모달 생성
1. 영화가 클릭될 때 모달을 열기 위해 호출되는 모달 생성 함수를 만든다.
그리고 Box 컴포넌트의 props로 전달한다.
useNavigate 훅을 사용하여 해당 영화의 상세설명 컴포넌트로 url 이동을 한다.
const navigate = useNavigate();
const onBoxClicked = (movieId: number) => {
navigate(`/movies/${movieId}`);
};
<Box
...
onClick={() => onBoxClicked(movie.id)}
></Box>;
2. 모달 컴포넌트(BigMovie)를 생성한다.
position을 fixed 시켜 스크롤의 움직임에 상관없이 모달의 위치를 고정한다.
const BigMovie = styled(motion.div)`
position: fixed;
height: 70vh;
top: 0;
bottom: 0;
left: 0;
right: 0;
`;
3. useMatch 훅을 사용하여 url 매칭을 확인한다.
url매칭이 true를 리턴할 경우 모달 컴포넌트가 렌더링 되도록 한다.
const bigMovieMatch: PathMatch<string> | null = useMatch("/movies/:movieId");
{bigMovieMatch ? (
<BigMovie layoutId={bigMovieMatch.params.movieId} />
) : null}
4. Box 컴포넌트와 모달 컴포넌트에 동일한 layoutId를 주어 transition를 적용한다.
모달 소멸
1. 오버레이 컴포넌트를 생성한다.
position을 fixed 시켜 컴포넌트의 위치를 화면 전체로 고정시킨다.
const Overlay = styled(motion.div)`
position: fixed;
top: 0;
width: 100%;
height: 100%;
background-color: rgb(0, 0, 0, 0.5);
opacity: 0;
`;
2. 오버레이 클릭할 때 home으로 돌아가도록 호출되는 onClick 함수를 생성한다.
오버레이 컴포넌트의 props로 전달한다.
const onOverlayClicked = () => {
navigate(`/`);
};
{bigMovieMatch ? (
<>
<Overlay
onClick={onOverlayClicked}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
<BigMovie layoutId={bigMovieMatch.params.movieId} />
</>
) : null}
모달 채우기
movieId를 사용하여
검색창 기능 구현하기
1. useForm 사용
2. submit 함수 navigate 사용 ?로 키워드 전달
3. useLocation 사용하여 url에서 keyword 확인
4. js의 URLSearchParameter을 사용하여 파싱
5. api에 요청
'Tech > TypeScript' 카테고리의 다른 글
[ TS 기초 ] 함수 - Call signiture / 오버로딩 / 제너릭 (0) | 2023.03.21 |
---|---|
[ TS 기초 ] 타입스크립트란 (0) | 2023.03.21 |
[Typescript / TS] Frame motion을 사용한 애니메이션 (0) | 2023.01.29 |
[Typescript / TS] ToDo 칸반보드 만들기 (0) | 2023.01.23 |
[Typescript / TS] ToDo리스트 만들기 (0) | 2023.01.16 |