[LG CNS AM CAMP 1기] 프론트엔드 10 | React

letthem·2025년 1월 8일
0

LG CNS AM CAMP 1기

목록 보기
10/16
post-thumbnail

RouterProvider 사용

App.js ⇒ RouterProvider 추가

import { BrowserRouter, createBrowserRouter, Route, RouterProvider, Routes } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import Profiles from "./Profiles";
import Profile from "./Profile";
import Layout from "./Layout";
import NotFound from "./NotFound";
import Login from "./Login";
import MyPage from "./MyPage";

const router = createBrowserRouter();

function App() {
  /*
  return (
    <BrowserRouter>
      <Routes>
        <Route element={<Layout />}>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/info" element={<About />} />
          <Route path="/profiles" element={<Profiles />}>
            <Route path=":userid" element={<Profile/>} />
          </Route>
          <Route path="/login" element={<Login />} />
          <Route path="/mypage" element={<MyPage />} />
        </Route>
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
  */
  return <RouterProvider router={router} />
}

export default App;

createBrowserRouter 함수에 인자 값으로 라우팅 정보를 전달

import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import Profiles from "./Profiles";
import Profile from "./Profile";
import Layout from "./Layout";
import NotFound from "./NotFound";
import Login from "./Login";
import MyPage from "./MyPage";

const router = createBrowserRouter([
  {
    path: "/", element: <Layout />, children: [
      { path: "/", element: <Home /> },
      { path: "/about", element: <About /> },
      { path: "/info", element: <About /> },
      { path: "/profiles", element: <Profiles />, children: [
        { path: ":userid", element: <Profile /> },
        ] 
      },
      { path: "/login", element: <Login /> },
      { path: "/mypage", element: <MyPage /> }
    ]
  },
  {path: "*", element: <NotFound/>}
]);

function App() {
  return <RouterProvider router={router} />
}

export default App;

영화 조회 서비스 제작

영화 제목으로 영화 정보를 조회하는 페이지
조회 결과 목록에서 특정 영화를 선택하면 영화 상세 페이지
영화 포스트, 시놉시스, 동영상 클릭 등을 제공
동영상을 선택하면 유튜브 영상을 실행

영화 정보 제공 API

영화 검색

API_themoviedb

{
  "page": 1,
  "results": [
    {
      "adult": false, <= 성인영화 여부
      "backdrop_path": "/4qCqAdHcNKeAHcK8tJ8wNJZa9cx.jpg", <= 배경 이미지
                       https://image.tmdb.org/t/p/w500/4qCqAdHcNKeAHcK8tJ8wNJZa9cx.jpg
      "genre_ids": [ <= 장르 ID 
                       https://api.themoviedb.org/3/genre/movie/list?api_key=9d2bff12ed955c7f1f74b83187f188ae		
        12,
        28,
        878
	  ],
      "id": 11, ⇐ 영화 ID
	  "original_language": "en", ⇐ 언어
      "original_title": "Star Wars", ⇐ 제목

      "overview": "Princess Leia is captured and held hostage by the evil Imperial forces in their effort to take over the galactic Empire. Venturesome Luke Skywalker and dashing captain Han Solo team together with the loveable robot duo R2-D2 and C-3PO to rescue the beautiful princess and restore peace and justice in the Empire.", ⇐ 시놉시스

      "popularity": 132.037,
      "poster_path": "/6FfCtAuVAW8XJjZ7eWeLibRLWTw.jpg", <= 포스터
                       https://image.tmdb.org/t/p/w500/6FfCtAuVAW8XJjZ7eWeLibRLWTw.jpg
      "release_date": "1977-05-25", <= 최초 상영일 
      "title": "Star Wars", <= 제목
      "video": false, <= 비디오 출시 여부
      "vote_average": 8.204, <= 평점
      "vote_count": 20747 <= 투표수
    },
    ...

비디오 클립 목록 조회

API_비디오 클립 목록 조회

https://api.themoviedb.org/3/movie/11/videos?api_key=***
                                  ~~~ 영화 ID

{
  "id": 11,				⇐ 영화 ID
  "results": [
    {					
      "iso_639_1": "en",
      "iso_3166_1": "US",
      "name": "Star Wars (1977) - Trailer",		⇐ 비디오 클립 제목
      "key": "XsS1yE2f-hE",				⇐ 비디오 클립 식별자
      "site": "YouTube",				⇐ 비디오 클립 제공 사이트
      "size": 1080,
      "type": "Trailer",
      "official": false,
      "published_at": "2022-05-25T16:00:29.000Z",
      "id": "63d1c26ecb71b800810237b7"
    },
    ...

유튜브 영상 실행

유튜브에 key(XsS1yE2f-hE 등)를 넣어서 소스 코드 받아오는 API가 있다!
react-youtube 컴포넌트를 이용

<YouTube videoid="XsS1yE2f-hE" />

사용할 패키지를 설치

npm install axios react-youtube

MovieList.js => 검색어와 일치하는 영화 목록을 가져와서 포스터 이미지, 영화 제목, 시놉시스 출력

import { useState } from 'react';
import axios from 'axios';

export default function MovieList() {
  const [title, setTitle] = useState('');

  const searchMovie = () => {
    const endpoint = `https://api.themoviedb.org/3/search/movie?api_key=9d2bff12ed955c7f1f74b83187f188ae&query=${title}`;
    axios
      .get(endpoint)
      .then((res) => console.log(res))
      .catch((err) => console.log(err));
  };

  return (
    <div>
      <h1>영화 조회</h1>
      <div>
        <input type="text" value={title} onChange={(e) => setTitle(e.target.value)} />
        <button onClick={searchMovie}>조회</button>
      </div>
    </div>
  );
}

App.js에 MovieList 컴포넌트와 관련한 라우트 정보를 추가

{ path: '/movieList', element: <MovieList /> },

Layout 컴포넌트에 영화 조회 메뉴를 추가

<li><Link to="/movieList">영화조회</Link></li>

MovieList 컴포넌트를 수정 => 검색 결과를 상태변수에 추가하고, 화면에 출력 ⭐️⭐️⭐️

import { useState } from 'react';
import axios from 'axios';
import './MovieList.css';

export default function MovieList() {
  const [title, setTitle] = useState('');
  const [movies, setMovies] = useState([]);
  const [isSearch, setIsSearch] = useState(false);

  const searchMovie = () => {
    setIsSearch(true);
    const endpoint = `https://api.themoviedb.org/3/search/movie?api_key=9d2bff12ed955c7f1f74b83187f188ae&query=${title}`;
    axios
      .get(endpoint)
      .then((res) => {
        console.log(res);
        setMovies(res.data.results);
      })
      .catch((err) => console.log(err));
  };

  return (
    <div className="container">
      <h1>영화 조회</h1>
      <div className="search">
        <input type="text" value={title} onChange={(e) => setTitle(e.target.value)} />
        <button onClick={searchMovie}>조회</button>
      </div>
      {isSearch && movies.length === 0 && <div>일치하는 영화가 존재하지 않습니다.</div>}
      {!isSearch && <div>검색할 영화 제목을 입력하세요.</div>}
      {movies.length !== 0 &&
        movies.map((m) => (
          <div className="movie">
            <div className="poster">
              <img src={`https://image.tmdb.org/t/p/w500${m.poster_path}`} />
            </div>
            <div className="text">
              <h1>{m.title}</h1>
              <p>{m.overview}</p>
            </div>
          </div>
        ))}
    </div>
  );
}

MovieDetail.js ⇒ 비디오 클립 목록을 출력하는 컴포넌트

import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios';

// http://localhost:3000/movieDetail/:movieid
export default function MovieDetail() {
  const params = useParams();
  const movieid = params.movieid;

  // 컴포넌트가 마운트되었을 때 비디오 클립을 조회
  useEffect(() => {
    const endpoint = `https://api.themoviedb.org/3/movie/${movieid}/videos?api_key=9d2bff12ed955c7f1f74b83187f188ae`;
    axios
      .get(endpoint)
      .then((res) => console.log(res))
      .catch((err) => console.log(err));
  }, []);

  return (
    <>
      <h1>데이터를 가져오고 있습니다.</h1>
    </>
  );
}

App.js ⇒ MovieDetail 컴포넌트와 관련한 라우트 정보를 추가

{ path: '/movieDetail/:movieid', element: <MovieDetail /> },

MovieList.js ⇒ 영화 제목을 클릭하면 MovieDetail 컴포넌트로 이동

<h1>
  <Link to={`/movieDetail/${m.id}`}>{m.title}</Link>
</h1>

MovieDetail ⇒ 조회 결과를 상태 변수에 저장하고, 상태 변수의 값을 화면에 출력

import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios';

// http://localhost:3000/movieDetail/:movieid
export default function MovieDetail() {
  const params = useParams();
  const movieid = params.movieid;

  const [videos, setVideos] = useState([]);
  const [isLoading, setIsLoading] = useState(true);

  // 컴포넌트가 마운트되었을 때 비디오 클립을 조회
  useEffect(() => {
    const endpoint = `https://api.themoviedb.org/3/movie/${movieid}/videos?api_key=9d2bff12ed955c7f1f74b83187f188ae`;
    axios
      .get(endpoint)
      .then((res) => {
        console.log(res);
        setVideos(res.data.results);
        setIsLoading(false);
      })
      .catch((err) => console.log(err));
  }, []);

  return (
    <div>
      {isLoading && <h1>데이터를 가져오고 있습니다.</h1>}
      {!isLoading && videos.length === 0 && <h1>등록된 비디오 클립이 존재하지 않습니다.</h1>}
      {!isLoading &&
        videos.length !== 0 &&
        videos.map((v) => (
          <div>
            {v.name} (출시일: {v.published_at.substring(0, 10)})
          </div>
        ))}
    </div>
  );
}

MovieDetail => 비디오 제목을 클릭하면 해당 비디오가 실행되도록 기능을 추가

import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios';
import YouTube from 'react-youtube';

const opts = {
  height: '390',
  width: '640',
  playerVars: {
    autoplay: 1,
  },
};

export default function MovieDetail() {
  const params = useParams();
  const movieid = params.movieid;

  const [videos, setVideos] = useState([]);
  const [isLoading, setIsLoading] = useState(true);

  // 컴포넌트가 마운트되었을 때 비디오 클립을 조회
  useEffect(() => {
    const endpoint = `https://api.themoviedb.org/3/movie/${movieid}/videos?api_key=9d2bff12ed955c7f1f74b83187f188ae`;
    axios
      .get(endpoint)
      .then((res) => {
        console.log(res);
        setVideos(res.data.results);
        setIsLoading(false);
      })
      .catch((err) => console.log(err));
  }, []);

  const [key, setKey] = useState('');

  return (
    <>
      <div>
        {isLoading && <h1>데이터를 가져오고 있습니다.</h1>}
        {!isLoading && videos.length === 0 && <h1>등록된 비디오 클립이 존재하지 않습니다.</h1>}
        {!isLoading &&
          videos.length !== 0 &&
          videos.map((v) => (
            <div style={{ fontSize: 20 }} onClick={() => setKey(v.key)}>
              {v.name} (출시일: {v.published_at.substring(0, 10)})
            </div>
          ))}
      </div>
      {key && <YouTube videoId={key} opts={opts} />}
    </>
  );
}

🔑

마운트되는 순간 axios.get해서 자동으로 가져오게 하기 위해 useEffect를 사용하고 의존성 배열에 빈 배열로 해서 자동으로 가져온다.


조별 리액트 프로젝트

#1 개인별로 REST API를 이용해서 서비스 화면(컴포넌트)을 구성(개인)

cf_public APIs
cf_public_APIs

#2 개인별로 만든 서비스 화면(컴포넌트)을 리액트 라우터를 이용해서 메뉴로 구성(팀 취합)

#3 사용한 API에 대한 설명(요청 데이터, 응답 데이터 구조)

나는 http://api.tvmaze.com/search/shows?q=${title} API를 사용해서 TVMaze 컴포넌트를 만들었다 !

TV 프로그램을 검색 안 했을 때


이렇게 아무것도 검색하지 않았을 때 어떤 API를 사용했는지, 응답 데이터 구조가 어떤지 나타냈다.

TV 프로그램을 검색했을 때


이렇게 hi라고 검색해서 조회를 누르면 api 쿼리스트링에 hi 라고 들어가 api 요청을 한다.
image.original로 화질이 image.medium보다 괜찮은 포스터를 불러와 좌측에 띄우고 우측엔 프로그램 정보를 제공했다.
모두 비어있는 값일 때는 00 정보가 없습니다 라고 처리를 하였다.
장르는 배열로 여러 개 들어오기도 해서 | 로 구분자를 지었다.


요약 부분이 길다면 높이를 지정하고 스크롤 가능하게 만들어 조금 더 깔끔한 UI를 보여주려고 했다.

조에서 모든 사람의 컴포넌트를 병합하는 과정에서 css를 전역적으로 설정하신 분도 계셔서 최대한 겹치지 않게 병합하려고 다른 분의 코드에 className을 붙이며 수정하였다.

Navbar는 이런식으로 만들었다. 000은 해당 컴포넌트를 만든 사람 이름이 들어간다 !
hover하면 scale을 줘 조금 더 커진다.
시간이 더 있었더라면 좀 더 잘 만들었을텐데 Navbar 만드랴 다른 사람 컴포넌트 합치랴 수정하랴 내 컴포넌트 만드랴 소통하랴 너무너무 바빴던 것 같다.
쉬는 시간 하나 없이 계속했던 것 같다.

Navbar와 App.js 라우팅 설정 및 컴포넌트 이름, url 이름 등을 미리 세팅해서 나중에 소스 코드만 받아 붙여넣을 수 있도록 하였다.

API key를 발급 받으신 분도 계셔서 .env 파일에 담아 유출되지 않도록 하였다.

11명의 컴포넌트를 시간 내에 다 병합해야 하는데 비대면이기 때문에 소통이 바로바로 안 되면 불편할까봐 소통도 열심히 하려고 바삐 움직였다..!

전체적으로 보면 이런 느낌 !

시간 관계 상 다음 날 발표를 하였고, 강사님께 UI가 예쁘다고 칭찬받았다 ㅎ..ㅎ
마지막까지 더 나은 결과물을 보여드리기 위해 계속해서 수정했던 우리팀 아쥬 칭찬해 👏👏

0개의 댓글