React: 영화 앱 만들기 연습

summereuna🐥·2022년 2월 25일
0

React JS

목록 보기
17/69

📝 Making Movie App


goal

영화 소개, 링크 걸어 더 많은 정보 볼 수 있는 Movie App을 만들어 보자.

  • 첫 화면: api 불러올 때 로딩화면 보여주다가 api 로딩 완료되면 모든 영화 리스트 보여주기
  • 영화 누르면 앱 안에서 페이지 전환해 상세 정보 보여주기

Hint

  • 영화 API 사용
    rating 9이상인 영화만 선택 & 연도별 정렬하여 최신 영화 먼저 오게한 api: https://yts.mx/api/v2/list_movies.json?minimum_rating=9&sort_by=year
  • 참고: api 사용 방법

my task

첫 화면

  1. api 불러올 때 로딩화면 보여주다가
import { useState } from "react";

function App() {
  const [loading, setLoading] = useState(true);
  return <div>{loading ? "loading..." : null}</div>;
}

export default App;
  1. api 로딩 완료되면 모든 영화 리스트 보여주고 로딩은 없애기
import { useEffect, useState } from "react";

function App() {
  const [loading, setLoading] = useState(true);
  const [api, setApi] = useState([]);
  useEffect(() => {
    fetch(
      "https://yts.mx/api/v2/list_movies.json?minimum_rating=9&sort_by=year"
    )
      .then((response) => response.json())
      .then((json) => {
        console.log(json);
      });
  }, []);
  return <div>{loading ? "loading..." : null}</div>;
}

export default App;


json을 콘솔로그 해서 보니 json.data.movies로 받으면 데이타 사용 가능하다.

useEffect(() => {
    fetch(
      "https://yts.mx/api/v2/list_movies.json?minimum_rating=9&sort_by=year"
    )
      .then((response) => response.json())
      .then((json) => {
        setMovies(json.data.movies);
        setLoading(false);
      });
  }, []);

이렇게 작성하면 된다.

then()보다 요즘 더 자주 사용하는 async-await을 사용해보자.

async-await을 사용하기 위해 getMovies라는 함수를 만들자.

  //async() 함수
  const getMovies = async () => {
    //api 페치한거에서 response 가져와서
    const response = await fetch(
      `https://yts.mx/api/v2/list_movies.json?minimum_rating=9&sort_by=year`
    );
    // 그 response한거에서 json()을 뽑아내서 json을 가져와서
    const json = await response.json();
    //setMovies모디파이어에 데이터 넘겨주면 된다.
    setMovies(json.data.movies);
    setLoading(false);
  };

  useEffect(() => getMovies(), []);

아니면 더 짧게 await 붙은 애들 ()로 김밥처럼 더 묶을 수도 있다.

const getMovies = async () => {
    const json = await (
      await fetch(
        `https://yts.mx/api/v2/list_movies.json?minimum_rating=9&sort_by=year`
      )
    ).json();
    setMovies(json.data.movies);
    setLoading(false);
  };
  useEffect(() => getMovies(), []);
  1. 화면에 보이기 출력
    movies 배열에 map()을 해서 컴포넌트화 하면 된다.
import { useEffect, useState } from "react";

function App() {
  const [loading, setLoading] = useState(true);
  const [movies, setMovies] = useState([]);
  //async() 함수
  const getMovies = async () => {
    const json = await (
      await fetch(
        `https://yts.mx/api/v2/list_movies.json?minimum_rating=9&sort_by=year`
      )
    ).json();
    setMovies(json.data.movies);
    setLoading(false);
  };
  useEffect(() => getMovies(), []);
  console.log(movies);
  return (
    <div>
      {loading ? (
        "loading..."
      ) : (
        <div>
          {movies.map((movie) => (
            <div key={movie.id}>
              <img src={movie.medium_cover_image} />
              <h2>{movie.title}</h2>
              <p>{movie.summary}</p>
              <ul>
                {movie.genres.map((genre) => (
                  <li key={genre}>{genre}</li>
                ))}
              </ul>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

export default App;

코드 정리: component를 아에 다른 파일로 빼서 정리해 주고 넘어가자.

1. Movie.js 파일 생성

2. Movie 컴포넌트 작성

  • 여기서는 div에 key 값을 줄 필요가 없다. 왜냐하면 Map 안에 있는 것이 아니기 때문에 ㅇㅇ!
  • 그리고 movie.를 뺀다. movie는 App.js로부터 받을 예정인 정보들이기 때문에 아직 정의되지 않았다.
  • 따라서 props object를 열어서 받아와야 한다.
    Movie 컴포넌트가 이 정보들(properties)을 parent component로 부터 받아 올 수 있도록 이렇게 작성하면 된다.
    function Movie({ medium_cover_image, title, summary, genres }) {...}
function Movie({ medium_cover_image, title, summary, genres }) {
  return (
    <div>
      <img src={medium_cover_image} />
      <h2>{title}</h2>
      <p>{summary}</p>
      <ul>
        {genres.map((genre) => (
          <li key={genre}>{genre}</li>
        ))}
      </ul>
    </div>
  );
}

export default Movie;

3. 프로퍼티 보내기

  • App.js에 Movie 컴포넌트 가져와 렌더해주자.
  • 그리고 프로퍼티를 보내주자.
  • 그리고 유니크한 key값 있어야 된다고 경고 뜨니까 key 도 넣어주자.

    key는 React.js에서만, map안에서 component들을 render할 때 사용하는 거다.

import Movie from "./Movie";
//생략
return (
    <div>
      {loading ? (
        "loading..."
      ) : (
        <div>
          {movies.map((movie) => (
            <Movie
              key={movie.id}
              coverImg={movie.medium_cover_image}
              title={movie.title}
              summary={movie.summary}
              genres={movie.genres}
            />
          ))}
        </div>
      )}
    </div>
  );
//생략

일반적으로 자바스크립트에서는 medium_cover_image이런 식으로 쓰지 않는다.
보통 mediumCoverImage 이렇게 카멜백 사용한다.
그런데 지금은 component니까 부르고 싶은대로 불러도 상관은 없지만 카멜백으로 coverImg 이런식으로 바꾸자..^ㅇ^~

  • 그러면 Movie.js의 컴포넌트의 props도 coverImg로 바꿔주면 된다.
  • img 태그에 alt 속성 넣어서 이 이미지가 무슨 이미지인지 정보도 주자.
function Movie({ coverImg, title, summary, genres }) {
  return (
    <div>
      <img src={coverImg} alt={title} />
      <h2>{title}</h2>
      <p>{summary}</p>
      <ul>
        {genres.map((genre) => (
          <li key={genre}>{genre}</li>
        ))}
      </ul>
    </div>
  );
}

export default Movie;

이렇게 하면 props으로 component로 넘겨서 data를 사용할 수 있게 된다.

4. 프롭타입 작성

  • Movie 컴포넌트의 props가 어떤 PropTypes 가져야 하는지 알 수 있도록 프롭타입을 설정하자.
import PropTypes from "prop-types";

function Movie({ coverImg, title, summary, genres }) {
  return (
    <div>
      <img src={coverImg} alt={title} />
      <h2>{title}</h2>
      <p>{summary}</p>
      <ul>
        {genres.map((genre) => (
          <li key={genre}>{genre}</li>
        ))}
      </ul>
    </div>
  );
}

//Movie 컴포넌트 PropTypes 설정
Movie.propTypes = {
  coverImg: PropTypes.string.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  //장르는 어레이가 되어야 한다. 그리고 어레이 안에 오는 엘리먼트는 string으로 작성
  genres: PropTypes.arrayOf(PropTypes.string).isRequired,
};

export default Movie;

React Router: 페이지 전환

각 영화 별로 movies/movie.id가 붙은 상세 페이지로 전환하기 위해 React Router를 사용해야 한다.

1. route별 코드 정리

이제 스크린(= 페이지, = route) 단위로 생각해야 한다. 즉 route 별로 코드를 정리해 줘야 한다.

  • Home 라우트: 모든 영화 보여주기
  • Detail 라우트: 영화 상세페이지 보여주기
  1. 이를 위해 새로운 폴더를 만들자.
  • src/routes 폴더 생성
  • 그리고 src/components폴더를 새로 만들어서 Movie 컴포넌트를 여기에 넣어 주자. 그러면 다행히 App.js에서 Movie.js를 import하는 path가 자동으로 수정해주기 때문에 저장하면 된다.
  1. src/routes 폴더에 Home.js route 생성
    Home.js 라우트는 모든 영화를 보여주는 페이지 이기 때문에 현재 App()에 적은 코드 싹 다 가지고 오면 된다.
    - 📍Home.js route
import { useEffect, useState } from "react";
import Movie from "../components/Movie";

function Home() {
  const [loading, setLoading] = useState(true);
  const [movies, setMovies] = useState([]);
  //async() 함수
  const getMovies = async () => {
    const json = await (
      await fetch(
        `https://yts.mx/api/v2/list_movies.json?minimum_rating=9&sort_by=year`
      )
    ).json();
    setMovies(json.data.movies);
    setLoading(false);
  };
  useEffect(() => getMovies(), []);
  console.log(movies);
  return (
    <div>
      {loading ? (
        "loading..."
      ) : (
        <div>
          {movies.map((movie) => (
            <Movie
              key={movie.id}
              coverImg={movie.medium_cover_image}
              title={movie.title}
              summary={movie.summary}
              genres={movie.genres}
            />
          ))}
        </div>
      )}
    </div>
  );
}

export default Home;
  1. src/routes 폴더에 Detail.js route 생성
    Detail.js 라우트는 각 영화의 상세 페이지를 보여주는 페이지이다.
    - 📍Detail.js route
function Detail() {
  return <h1>Detail</h1>;
}

export default Detail;

2. react-router-dom으로 URL 설정하기

npm install react-router-dom@6를 설치한다.

react-router-dom은 컴포넌트 모음집으로 react-router-dom을 사용하기 위해서 App.js 파일에 몇 가지를 import해야 한다.

<BrowserRouter> 컴포넌트

Router에는 두 가지(HashRouter, BrowserRouter)가 있는데 일반적으로 BrowserRouter를 사용한다.
BrowserRouter의 URL은 보통 웹사이트 처럼 https://movie.com/movie 이런 식이다.
HashRouter의 URL은 https://movie.com/#/movie 이런 식이다. URL 앞에 말그대로 해시태그(#)가 붙는다.

<Routes> 컴포넌트

<Route> 컴포넌트

예시: react-router-dom ✅ v6

import { render } from "react-dom";
import {
  BrowserRouter,
  Routes,
  Route,
} from "react-router-dom";
// import your route components too

render(
  <BrowserRouter>
    <Routes>
      <Route path="/" element={<App />}>
        <Route index element={<Home />} />
        <Route path="teams" element={<Teams />}>
          <Route path=":teamId" element={<Team />} />
          <Route path="new" element={<NewTeamForm />} />
          <Route index element={<LeagueStandings />} />
        </Route>
      </Route>
    </Routes>
  </BrowserRouter>,
  document.getElementById("root")
);

🔥 react-router-dom이 v.6으로 바뀌면서 바뀐 부분

  • <Switch> 컴포넌트 👉 <Routes> 컴포넌트로 대체
    v.6 부터는, <Switch>컴포넌트가 <Routes>컴포넌트로 대체되었다. <Routes>가 최적의 경로배정을 해주기 때문에 <Switch>를 썼을 때의 고민을 말끔히 해결해 준다.
    -<Route> 의 exact 속성도 더이상 쓰이지 않는다.
  • 또한 <Routes>컴포넌트 사이에 자식 컴포넌트를 넣지 않고, element prop에 자식 컴포넌트를 할당하도록 바뀌었다.
    Route path="/" element={< Home / >}

v.5 vs v.6 비교

✅ react-router-dom v.5.3.0

// npm i react-router-dom@5.3.0
// <Router>는 URL을 보고 있는 component이다.
// <Switch>는 Route(URL을 의미함)를 찾는 컴포넌트이다. <Switch> 컴포넌트가 Route를 찾으면 컴포넌트를 렌더한다.

import Home from "./routes/Home";
import Detail from "./routes/Detail";
import { BrowserRouter, Router, Switch, Route } from "react-router-dom";

function App() {
  return (
    <Router>
      <Switch>
        <Route path="/">
    		<Home />
    	</Route>
        <Route path="/movie">
    		<Detail />
    	</Route>
      </Switch>
    </Router>
  );
}

export default App;

✅ react-router-dom v.6

// npm i react-router-dom@6

import Home from "./routes/Home";
import Detail from "./routes/Detail";
import { BrowserRouter, Routes, Route } from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />}></Route>
        <Route path="/movie" element={<Detail />}></Route>
      </Routes>
    </BrowserRouter>
  );
}

export default App;

👉 v.6로 진행하자.

route는 이렇게 구성하면 된다.

  • home URL /: loading과 movies 보여주는 컴포넌트 렌더
  • detail URL /movie: Detail이라고 h1 태그 렌더하는 컴포넌트 렌더

유저가 영화 제목을 클릭하면 Detail 페이지로 가게 만들어 보자.

  • <h2><a href="/movie">{title}</a></h2>
    이런식으로 Movie 컴포넌트가 렌더하는 {title}에 a 태그 달아서 이동해도 되지만, 그러면 페이지 전체가 새고된다.
    다행히 전체 새고 안되게 이동하는 컴포넌트가 있다.

  • <Link> 컴포넌트
    한 route에서 다른 route로 (다른 URL로) 가고 싶을 때, <Link> 컴포넌트는 브라우저 새고 없이 유저를 다른 페이지로 이동시켜 준다.

  • 예시

import { Link } from "react-router-dom";

function Home() {
  return (
    <div>
      <h1>Home</h1>
      <nav>
        <Link to="/">Home</Link> |{" "}
        <Link to="about">About</Link>
      </nav>
    </div>
  );
}

따라서 Movie 컴포넌트에 Link를 import 해와서 사용하면 된다.

- 📍 Movie.js

import PropTypes from "prop-types";
import { Link } from "react-router-dom";

function Movie({ coverImg, title, summary, genres }) {
  return (
    <div>
      <img src={coverImg} alt={title} />
      <h2>
        <Link to="/movie">{title}</Link>
      </h2>
      <p>{summary}</p>
      <ul>
        {genres.map((genre) => (
          <li key={genre}>{genre}</li>
        ))}
      </ul>
    </div>
  );
}

//생략

페이지 새고는 안되면서 다른 url로 잘 넘어간다!


요렇게 App 컴포넌트에 만들었던 모든 로직을 별개의 장소로 옮겼다.
Home 컴포넌트 따로, Detail 따로, Movie 따로 ~
App에서는 react-router-dom의 컴포넌트(url 바라보는 컴포넌트들)를 가져다 쓰고 있다.
url이 바뀌면 어떤 컴포넌트 보여줄지 넣어 뒀다. 유저가 "/" path에 있으면 Home 컴포넌트 보여줘라 이렇게.
<Route path="/hey" element={<h1>BTS</h1>}></Route> 이런식으로도 사용할 수 있다. 그러면 /hey로 가면 h1태그로 BTS라고 렌더된다.

profile
Always have hope🍀 & constant passion🔥

0개의 댓글