티스토리 뷰
1. App.tsx
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Header from "./Routes/Header";
import Home from "./Routes/Home";
import Search from "./Routes/Search";
import Tvs from "./Routes/Tvs/tvs";
import Movies from "./Routes/Movies/movies";
import Latest from "./Routes/latest";
function App() {
return (
<Router>
<Header />
<Switch>
<Route path="/search">
<Search />
</Route>
<Route path={["/tvs", "/tvs/:tvId"]}>
<Tvs />
</Route>
<Route path="/search">
<Search />
</Route>
<Route path={["/movies", "/movies/:movieId"]}>
<Movies />
</Route>
<Route path="/latest">
<Latest />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
);
}
export default App;
이 부분은 강의의 코드와 크게 다르지 않다. 개인적으로 수정하면서 더 필요하다 생각한 path를 추가한 것이 끝이다. Router를 통해 url에 따라 다른 것들을 렌더링한다. 이를 결정하는 것들은 모두 <Route path="~">에서 확인할 수 있다. 예를 들어, /latest의 경우 <Latest />를 렌더링한다.
2. Header.tsx
import styled from "styled-components";
import { Link, useRouteMatch, useHistory } from "react-router-dom";
import { motion, useAnimation, useViewportScroll } from "framer-motion";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
const Nav = styled(motion.nav)`
display: flex;
justify-content: space-between;
align-items: center;
position: fixed;
width: 100%;
top: 0;
background-color: black;
font-size: 14px;
padding: 20px 60px;
color: white;
`;
const Col = styled.div`
display: flex;
align-items: center;
`;
const Logo = styled(motion.svg)`
margin-right: 50px;
width: 95px;
height: 25px;
fill: ${(props) => props.theme.red};
path {
stroke-width: 6px;
stroke: white;
}
`;
const Items = styled.ul`
display: flex;
align-items: center;
`;
const Item = styled.li`
margin-right: 20px;
color: ${(props) => props.theme.white.darker};
transition: color 0.3s ease-in-out;
position: relative;
display: flex;
justify-content: center;
flex-direction: column;
&:hover {
color: ${(props) => props.theme.white.lighter};
}
`;
const Search = styled.form`
color: white;
display: flex;
align-items: center;
position: relative;
svg {
height: 25px;
}
`;
const Circle = styled(motion.span)`
position: absolute;
width: 5px;
height: 5px;
border-radius: 5px;
bottom: -5px;
left: 0;
right: 0;
margin: 0 auto;
background-color: ${(props) => props.theme.red};
`;
const Input = styled(motion.input)`
transform-origin: right center;
position: absolute;
right: 0px;
padding: 5px 10px;
padding-left: 30px;
z-index: -1;
color: white;
font-size: 16px;
background-color: transparent;
width: 250px;
border: 1px solid ${(props) => props.theme.white.lighter};
`;
const logoVariants = {
normal: {
fillOpacity: 1,
},
active: {
fillOpacity: [0, 1, 0],
transition: {
repeat: Infinity,
},
},
};
const navVariants = {
top: {
backgroundColor: "rgba(0, 0, 0, 1)",
},
scroll: {
backgroundColor: "rgba(0, 0, 0, 0)",
},
};
interface IForm {
keyword: string;
}
function Header() {
const { register, handleSubmit } = useForm<IForm>();
const history = useHistory();
const onValid = (data: IForm) => {
history.push(`/search?keyword=${data.keyword}`);
};
const homeMatch = useRouteMatch("/");
const movieMatch = useRouteMatch("/movies");
const tvMatch = useRouteMatch("/tvs");
const latestMatch = useRouteMatch("/latest");
const [searchOpen, setSearchOpen] = useState(false);
const inputAnimation = useAnimation();
const navAnimation = useAnimation();
const { scrollY } = useViewportScroll();
const toggleSearch = () => {
if (searchOpen) {
inputAnimation.start({ scaleX: 0 });
} else {
inputAnimation.start({ scaleX: 1 });
}
setSearchOpen((prev) => !prev);
};
useEffect(() => {
scrollY.onChange(() => {
if (scrollY.get() > 80) {
navAnimation.start("scroll");
} else {
navAnimation.start("top");
}
});
}, [scrollY, navAnimation]);
return (
<Nav variants={navVariants} animate={navAnimation} initial={"top"}>
<Col>
<Link to="/">
<Logo
variants={logoVariants}
whileHover="active"
animate="normal"
xmlns="http://www.w3.org/2000/svg"
width="1024"
height="276.742"
viewBox="0 0 1024 276.742"
>
<motion.path d="M140.803 258.904c-15.404 2.705-31.079 3.516-47.294 5.676l-49.458-144.856v151.073c-15.404 1.621-29.457 3.783-44.051 5.945v-276.742h41.08l56.212 157.021v-157.021h43.511v258.904zm85.131-157.558c16.757 0 42.431-.811 57.835-.811v43.24c-19.189 0-41.619 0-57.835.811v64.322c25.405-1.621 50.809-3.785 76.482-4.596v41.617l-119.724 9.461v-255.39h119.724v43.241h-76.482v58.105zm237.284-58.104h-44.862v198.908c-14.594 0-29.188 0-43.239.539v-199.447h-44.862v-43.242h132.965l-.002 43.242zm70.266 55.132h59.187v43.24h-59.187v98.104h-42.433v-239.718h120.808v43.241h-78.375v55.133zm148.641 103.507c24.594.539 49.456 2.434 73.51 3.783v42.701c-38.646-2.434-77.293-4.863-116.75-5.676v-242.689h43.24v201.881zm109.994 49.457c13.783.812 28.377 1.623 42.43 3.242v-254.58h-42.43v251.338zm231.881-251.338l-54.863 131.615 54.863 145.127c-16.217-2.162-32.432-5.135-48.648-7.838l-31.078-79.994-31.617 73.51c-15.678-2.705-30.812-3.516-46.484-5.678l55.672-126.75-50.269-129.992h46.482l28.377 72.699 30.27-72.699h47.295z" />
</Logo>
</Link>
<Items style={{ fontWeight: "bold" }}>
<Item>
<Link to="/">
Home {homeMatch?.isExact && <Circle layoutId="circle" />}
</Link>
</Item>
<Item>
<Link to="/movies">
Movie {movieMatch && <Circle layoutId="circle" />}
</Link>
</Item>
<Item>
<Link to="/tvs">
Tv Shows {tvMatch && <Circle layoutId="circle" />}
</Link>
</Item>
<Item>
<Link to="/latest">
Latest {latestMatch && <Circle layoutId="circle" />}
</Link>
</Item>
</Items>
</Col>
<Col>
<Search onSubmit={handleSubmit(onValid)}>
<motion.svg
onClick={toggleSearch}
animate={{ x: searchOpen ? -220 : 0 }}
transition={{ type: "linear" }}
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
clipRule="evenodd"
></path>
</motion.svg>
<Input
{...register("keyword", { required: true, minLength: 2 })}
animate={inputAnimation}
initial={{ scaleX: 0 }}
transition={{ type: "linear" }}
placeholder="Search for movie or tv show..."
/>
</Search>
</Col>
</Nav>
);
}
export default Header;
기존의 강의 코드에서 별도로 추가된 path에 따른 Link를 추가하였다.
- const { register, handleSubmit } = useForm<IForm>(); : 단어 검색을 위한 react-hook-form이다.
- const history = useHistory(); : 단어를 검색했을 때, 그 단어를 이용해 url 수정을 위해 필요.
- const homeMatch = useRouteMatch("/"); 등의 useRouterMatch: 현재 어느 페이지를 보고 있는지 확인하기 위해 필요.
3. genres.tsx
export const movieGenres = [
{
id: 28,
name: "Action",
},
{
id: 12,
name: "Adventure",
},
{
id: 16,
name: "Animation",
},
{
id: 35,
name: "Comedy",
},
{
id: 80,
name: "Crime",
},
{
id: 99,
name: "Documentary",
},
{
id: 18,
name: "Drama",
},
{
id: 10751,
name: "Family",
},
{
id: 14,
name: "Fantasy",
},
{
id: 36,
name: "History",
},
{
id: 27,
name: "Horror",
},
{
id: 10402,
name: "Music",
},
{
id: 9648,
name: "Mystery",
},
{
id: 10749,
name: "Romance",
},
{
id: 878,
name: "Science Fiction",
},
{
id: 10770,
name: "TV Movie",
},
{
id: 53,
name: "Thriller",
},
{
id: 10752,
name: "War",
},
{
id: 37,
name: "Western",
},
];
export const getMovieGenre = (genreId: number) => {
let genreName = "";
genreName = movieGenres.find((genre) => genre.id === genreId).name;
return genreName;
};
export const tvGenres = [
{
id: 10759,
name: "Action & Adventure",
},
{
id: 16,
name: "Animation",
},
{
id: 35,
name: "Comedy",
},
{
id: 80,
name: "Crime",
},
{
id: 99,
name: "Documentary",
},
{
id: 18,
name: "Drama",
},
{
id: 10751,
name: "Family",
},
{
id: 10762,
name: "Kids",
},
{
id: 9648,
name: "Mystery",
},
{
id: 10763,
name: "News",
},
{
id: 10764,
name: "Reality",
},
{
id: 10765,
name: "Sci-Fi & Fantasy",
},
{
id: 10766,
name: "Soap",
},
{
id: 10767,
name: "Talk",
},
{
id: 10768,
name: "War & Politics",
},
{
id: 37,
name: "Western",
},
];
export const getTvGenre = (genreId: number) => {
let genreName = "";
genreName = tvGenres.find((genre) => genre.id === genreId).name;
return genreName;
};
장르는 직접 id와 genre를 작성하였다. 물론, 이 역시 api.ts에서 api를 이용하여 장르를 불러올 수 있다. 하지만 장르의 양이 얼마 되지 않는다고 생각하여 직접 복사, 붙여넣기를 하였다.
'기록' 카테고리의 다른 글
React Movies 기록 - 5. Search (0) | 2023.02.05 |
---|---|
React Movies 기록 - 4. movies, movie (0) | 2023.02.05 |
React Movies 기록 - 3. Home, latest (0) | 2023.02.05 |
React Movies 기록 - 1. 개요, 구조 (0) | 2023.02.05 |
기록) GitHub error 해결 과정 (0) | 2023.01.08 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 완전탐색
- NextJS
- typescript
- 스택
- 리액트
- 타입스크립트
- 동적계획법
- 백준
- BFS
- aws
- CSS
- 브루트포스
- react
- Redux
- 순열
- async
- 비트마스킹
- 알고리즘
- 햄버거버튼
- 카카오맵
- 넥스트js
- Next.js
- C++
- 프로그래머스
- 다이나믹프로그래밍
- SQL
- 자바스크립트
- 구현
- themoviedb
- 코드스테이츠
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
글 보관함