웹 앱에서 동적 라우팅을 구현할 수 있다.
라우팅이 실행 중인 앱 외부의 구성에서 처리되는 기존 라우팅 아키텍처와 달리 React Router DOM은 앱 및 플랫폼의 요구 사항에 따라 컴포넌트 기반 라우팅을 용이하게 합니다.
리액트는 SPA!!
npm install react-router-dom --save
yarn add react-router-dom
설치가 완료된 후 가장 먼저 할 일은 앱 어디에서나 React Router를 사용할 수 있도록 하는 것
이렇게 하려면 src폴더에서 index.js 파일을 열고 react-router-dom에서 BrowserRouter를 가져온 다음 루트 구성요를 그 안에 래핑
여러 컴포넌트 생성 및 라우트 정의하기
To로 가기때문에 Link랑 비슷한듯..!?
useNavigate
경로를 바꿔준다.
useParams
style 문법을 path경로에서 사용하였다면 useParams()로 읽을 수 있다.
아래는 :invoicedId가 무엇인지 알기위해 useParams를 사용했다.
이 Hooks는 현재 위치 객체를 반환한다. 이것은 현재 위치가 변경될 때마다 일부 side effect를 수행하려는 경우에 유용할 수 있다.
Django를 사용할때처럼 파트별로 파일을 빼내는것이다.
그래서
import './App.css';
import Nav from "./components/Nav";
import Footer from './components/Footer';
import { Outlet, Route, Routes } from 'react-router-dom';
import DetailPage from './pages/DetailPage';
import MainPage from './pages/MainPage';
import SearchPage from './pages/SearchPage';
const Layout = () =>{
return(
<div>
<Nav/>
<Outlet/>
<Footer/>
</div>
)
}
function App() {
return (
<div className="App">
<Routes>
<Route path='/' element={<Layout/>}>
<Route index element={<MainPage/>} />
<Route path=":movieId" element={<DetailPage/>} />
<Route path="search" element={<SearchPage/>} />
</Route>
</Routes>
</div>
);
}
export default App;
라우터로 감싸주고 페이지리를 불러오는 형식이다.
path같은 경우는 도메인뒤에 붙는 것이라고 생각하면 된다.
Layout에는 모든 페이지에 공통적으로 들어갈 Navbar,footer를 넣고 그 외에는 pages에 따로 빼준다.
이런 식으로 말이다!
그래서 MainPage/index.js 파일을 한번 보면
import React from 'react'
import Banner from '../../components/Banner'
import Row from '../../components/Row'
import requests from '../../api/requests'
export default function MainPage() {
return (
<div>
<Banner/>
<Row
title="NETFLIX ORIGINALS"
id="NO"
fetchUrl={requests.fetchNetflixOriginals}
isLargeRow
/>
<Row
title="Trending Now"
id = "TN"
fetchUrl={requests.fetchTrending}
/>
<Row
title="Top Rated"
id = "TR"
fetchUrl={requests.fetchTopRated}
/>
<Row
title="Action Movies"
id = "AM"
fetchUrl={requests.fetchActionMovies}
/>
<Row
title="Comnedy Movies"
id = "CM"
fetchUrl={requests.fetchComedyMovies}
/>
</div>
)
};
이런 식으로 작성하면된다.
그런데 여기서 끝이 아니라 index.js로 가서
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
이런식으로 BrowserRouter로 감싸줘야한다.
<input value={searchValue} onChange={handleChange} className="nav__input" type="text" placeholder='영화제목을 입력해주세요.'></input>
input을 생성해준다.
생성해주고css를 입혀주고
const [searchValue, setsearchValue] = useState("");
const navigate = useNavigate();
const handleChange = (e) => {
setsearchValue(e.target.value); //바로 검색가능하다.
navigate(`/search?q=${e.target.value}`)
};
함수를 줘서 입력하면 바로 검색되게 만든다.
이제 경로에서 value를 가져와서 전달해주자.
이때 useLocation을 사용한다.
q에 있는걸 가져오게 하면 다음과 같이 뜬다.
import React from 'react'
import { useLocation } from 'react-router-dom'
export default function SearchPage() {
console.log('useLocation() : ',useLocation());
const useQuery =()=>{
return new URLSearchParams(useLocation().search);
}
let query = useQuery();
const searchTerm = query.get("q");
return (
<div>SearchPage</div>
)
}
import axios from "../../api/axios";
import React, { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom'
export default function SearchPage() {
const [searchResult, setsearchResult] = useState([]);
// console.log('useLocation() : ',useLocation());
const useQuery =()=>{
return new URLSearchParams(useLocation().search);
}
let query = useQuery();
const searchTerm = query.get("q"); //q에 있는걸 가져온다.
// console.log(searchTerm)
useEffect(() => {
if(searchTerm){
fetchSearchMovie(searchTerm);
}
}, [searchTerm]);
const fetchSearchMovie = async (searchTerm) =>{
try{
const request = await axios.get(
`/search/multi?include_adult=false&query=${searchTerm}`
)
setsearchResult(request.data.results);
}catch(error){
console.log("ERROR",error);
}
}
return (
<div>SearchPage</div>
)
}
매번 검색될때마다 새로운 영화데이터를가져온다.
그러면 영화가 있을때와 없을때의 UI를 이제 작성해주자.
이는 삼항연산자를 통해 해결했다.
import axios from "../../api/axios";
import React, { useEffect, useState } from 'react';
import { useLocation, } from 'react-router-dom'
import "./SearchPage.css";
export default function SearchPage() {
const [searchResult, setsearchResult] = useState([]);
const useQuery =()=>{
return new URLSearchParams(useLocation().search);
};
let query = useQuery();
const searchTerm = query.get("q"); //q에 있는걸 가져온다.
// console.log(searchTerm)
useEffect(() => {
if(searchTerm){
fetchSearchMovie(searchTerm);
}
}, [searchTerm]);
const fetchSearchMovie = async (searchTerm) =>{
try{
const request = await axios.get(
`/search/multi?include_adult=false&query=${searchTerm}`
)
setsearchResult(request.data.results);
}catch(error){
console.log("ERROR",error);
}
}
const renderSearchResults = () =>{
return searchResult.length > 0 ? (
<section className="search-container">
{searchResult.map((movie)=>{
if(movie.backdrop_path !== null && movie.media_type !== "person"){
const movieImageUrl =
"https://image.tmdb.org/t/p/w500" + movie.backdrop_path;
return(
<div className="movie">
<div className="movie__column-poster">
<img src={movieImageUrl} alt="movie" className="movie__poster">
</img>
</div>
</div>
)
}
})}
</section>
) : (
<section className="no-results">
<div className="no-results__text">
<p>
찾고자하는 검색어"{searchTerm}"에 맞는 영화가 없습니다.
</p>
</div>
</section>
);
};
return renderSearchResults();
}
불필요한 로드를 줄여준다.
hooks에 useDebouce.js파일을 만들어준다.
import { useState,useEffect } from 'react'
export const useDebounce=(value,delay)=>{
const [DebounceValue,setDebounceValue] = useState(value);
useEffect(() => {
const handler = setTimeout(()=>{
setDebounceValue(value)
},delay);
return () => {
clearTimeout(handler);
};
}, [value, delay])
return DebounceValue;
}
이를 가져가서 searchPage에서 사용하면된다.
const debouncedSearchTerm = useDebounce(searchTerm, 500);
이런 방식으로 말이다!
그리고, 나머지 searchTerm을 debouncedSearchTerm으로 바꿔주고 movie마다 key를 주면
에러없이 매번 로드하는것이 아니라 내가 정해놓은 시간마다 로드하는것을 확인할 수 있다.
<div onClick={()=>navigate(`/${movie.id}`)} className="movie__column-poster">
이를 통해서 영화detail페이지로 영화클릭시 이동 가능하다.
그 이후
detailpage의 index.js 파일을 작성해주자
import axios from "../../api/axios"
import React, { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom'
export default function DetailPage() {
const {movieId} = useParams();
const [movie, setMovie] = useState({});
useEffect(() => {
async function fetchData(){
const request = await axios.get(
`/movie/${movieId}`
)
setMovie(request.data)
}
fetchData();
}, [movieId])
if(!movie) return <div>...loading</div>;
return <section>
<img
className="modal__poster-img"
src={`https://image.tmdb.org/t/p/original/${movie.backdrop_path}`}
alt="poster"
/>
</section>
}
이와 같이 작성하면
이미지가 있는 경우 이렇게 이미지를 뜨게 만들어준다.