저번에는 단위 변환과 관련된 코드를 클론코딩하며 따라쳐보는 시간을 가졌다면, 오늘 부터는 영화에 대한 목록을 만드는 시간이다.
이전까지의 React 실습시간에서는 App.js에서 모든 화면을 구성하도록 처리했지만, 그렇다. 이제 드디어 프론트의 꽃 구조화 (개인적인 내 생각, 사실 Vue 했을 때도 구조화가 제일 재밌었음) 의 시간이 온 것이다.
Router
: 라우터를 사용하면 단일 페이지 애플리케이션(SPA)에서 사용자가 다른 URL로 이동할 때 페이지를 새로고침하지 않고도 콘텐츠를 동적으로 렌더링할 수 있다.
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Home from "./routes/Home";
import Detail from "./routes/Detail";
function App() {
return (
<Router>
<Routes>
<Route path="/movie/:id" element={<Detail />} />
<Route path="/" element={<Home />} />
</Routes>
</Router>
);
}
export default App;
먼저 Router를 사용하여 변화된 App.js 창의 모습이다. 기존에 이 페이지에 모든 정보를 다 입력했다면 Component화 시키고, 직관적으로 이 부분에 어떤 페이지가 들어오는지 보여주게 된다.
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
아래 사진과 같이 components, routes로 폴더를 나누어 해당하는 부분을 구현했다. 라고 생각하면 좋을 듯. (이전에는 App.js에 한꺼번에 다 박았음.) 조금 더 자세히 볼 것은 Home과 Movie이다.
Home에서는 2가지를 설명해야한다.
...
const getMovies = async () => {
const json = await (
await fetch(
`https://yts.mx/api/v2/list_movies.json?minimum_rating=8.5&sort_by=year`
)
).json();
useEffect(() => {
getMovies();
}, []);
async 함수 정의: async 키워드는 함수가 비동기적인 동작을 수행한다는 것을 의미합니다. async 함수는 항상 Promise를 반환한다.
첫 번째 await 사용: fetch 함수는 네트워크 요청을 보내고 응답을 받기까지 시간이 걸린다. fetch는 Promise를 반환하며, 이 Promise는 네트워크 응답이 완료되었을 때 해결된다. await 키워드는 fetch 함수가 완료될 때까지 코드 실행을 잠시 멈추고, 네트워크 응답이 완료되면 계속 진행하게 한다.
두 번째 await 사용: fetch의 결과는 Response 객체다. 이 객체의 .json() 메서드는 응답 본문을 JSON 형식으로 변환하는 작업을 수행한다. 이 메서드 또한 Promise를 반환하므로, 여기에도 await를 사용하여 JSON 변환 작업이 완료될 때까지 기다린다.
결과 처리: JSON으로 변환된 데이터는 json 변수에 저장되며, 이후 console.log(json)을 통해 콘솔에 출력된다.
// Home.js
import Movie from "../components/Movie"
return (
<div>
{loading ? (
<h1>Loading... </h1>
) : (
<div>
{movies.map((movie) => (
<Movie
key={movie.id}
id={movie.id}
coverImg={movie.medium_cover_image}
title={movie.title}
summary={movie.summary}
genres={movie.genres}
/>
))}
</div>
)}
</div>
);
Home.js에서 movies.map 안에 movie를 담아 Movie 컴포넌트에 아래와 같은 값 (key,id,coverImg...) 들을 담아서 전달한다.
//Movie.js
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
function Movie({ id,coverImg, title, summary, genres }) {
return (
<div>
<img src={coverImg} alt={title} />
<h2>
<Link to={`/movie/${id}`}> {title}</Link>
</h2>
<p>{summary}</p>
<ul>
{genres.map((g) => (
<li key={g}>{g}</li>
))}
</ul>
</div>
);
}
Movie.propTypes = {
id : PropTypes.number.isRequired,
coverImg: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
summary: PropTypes.string.isRequired,
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
};
export default Movie;
props 받아온 정보들 (id, coverImg 등등..)을 function함수에 넣어놓고, 자유롭게 사용할 수 있다.
또한 PropTypes를 통해, 자료형을 미리 설정해놓고, 사용할 수 있다.
마치며,
겪었던 시행착오
: react-router-dom과 react app의 버젼차이 때문에 Router, Link가 올바르게 연결되지 않았던 문제가 있었다.
function App() {
return <Router>
<Switch>
<Route path="/movie">
<Detail/>
</Route>
<Route path="/">
<Home/>
</Route>
</Switch>
</Router>
}
function App() {
return (
<Router>
<Routes>
<Route path="/movie/:id" element={<Detail />} />
<Route path="/" element={<Home />} />
</Routes>
</Router>
);
}
클론코딩하고 있던 강의는 react-router-dom v5의 설정을 따라하고 있었는데, 나의 경우 react-router-dom v6을 사용하고 있었다.
(현재 나의 react-router-dom의 버젼을 확인하는 방법은 pacakage.json 파일에서 확인 가능하다.)
그렇기에 기존의 스위치 태그로 화면을 전환 하는것이 아닌 Route 태그 안에서 모든 것을 처리하게 변했다. 훨씬 더 간단하게 보여지기 때문인 듯. 위에서 아래로 라우터의 구조가 바뀌었다고 생각하면 편할 듯.
이제 기본적인 인강을 통한 클론 코딩은 마무리 되었다. 다른 지식으로 찾아올 예정.