Directory

데이터를 가져왔으니 본격적으로 프론트 작업을 해보쟈!

전체적인 디렉토리의 구조
src폴더 내부에는 컴포넌트의 구조와 상태를 작성할 폴더를 Component / Container로 나눠 로직의 재사용성을 높혀주고 스타일링을 담당하는 폴더인 static과 Redux를 작성할 폴더인 store로 구성했다.

image.png

/Components


Presentational Component로 상태를 갖고 있지 않고 부모에게 props를 전달받아 데이터를 사용하고 Style을 담당하는 컴포넌트를 담을 폴더이다.

image.png

기본적인 레이아웃인 Nav / Footer / Header 와 데이터를 받아와 사용되는 Movie / Modal / NetflixMovie.jsx 파일 폴더로 구성했다.

Nav.jsx

    return (
        <nav className={"navigation " + (scrolling ? "black" : "" )}>
            <ul className="navigation_container">
                <div className="navigation_container left">
                    <img className="navigation_container logo" src={NetflixLogo} alt="logo" />
                    <div className="navigation_container link">Home</div>
                    <div className="navigation_container link">TV Shows</div>
                    <div className="navigation_container link">Movies</div>
                    <div className="navigation_container link">Recently</div>
                    <div className="navigation_container link">My List</div>
                </div>
                <div className="icons">
                    <div className="search-box">
                        <a className="searchLogo"><FiSearch/></a>
                        <input className="search-txt" type="text" placeholder=" Title, genres"/>
                    </div>
                    <div className="navigation_container link"><FiGift/></div>
                    <div className="navigation_container link"><FiBell/></div>
                    <div className="navigation_container link"><FiSmile/></div>
                </div>

            </ul>
        </nav>
    );
}

export default Nav;

Nav의 구조로 로고와 메뉴들 아이콘으로 구성되어있다.
Icon은 react-icons를 사용했고 Link에 Router를 적용할 예정이다.
yarn add react-icons react-router-dom

    // change nav bg color
    const [scrolling, setScrolling] = useState(false);

    const handleScroll = () => {
        if(window.scrollY === 0) {
            setScrolling(false);
        } else if (window.scrollY > 50) {
            setScrolling(true);
        }
    }

    window.addEventListener('scroll', handleScroll);

화면을 스크롤 하는 순간 opacity: 0인 Nav의 background가 opacity: 1로 바뀌는 이벤트를 적용했다.
window.scrollY 값을 통해 scrolling이 true일 때 backgroundColor가 바뀌는 이벤트를 window.addEventListener를 사용해 적용했다.

scrolling = false
image.png
scrolling = true
image.png

Header의 구조는 Background에 이미지가 꽉차있는 레이아웃으로 구성되어있고 내가 좋아하는 기묘한이야기를 메인으로 정했다.ㅎ

Header.jsx

<header>
                <div className="headerContainer">
                    <img src={backgroundImg} alt="stranger"/>
                    <div className="headerContents">
                        <h1>STRANGER THINGS</h1>
                        <p>When a young boy vanishes, a small town uncovers a mystery involving secret experiments, terrifying supernatural forces, and one strange little girl.</p>
                        <div className="btn">
                            <button><FiPlay/><span>PLAY</span></button>
                            <button><FiPlus/><span>MY LIST</span></button>
                            <button><FiInfo/><span>DETAILS</span></button>
                        </div>
                    </div>
                </div>
            </header>

image.png

headerContents에는 해당 영화에 대한 Title / Overview / button 이 왼쪽 하단에 들어간다.

Movie 컴포넌트는 각 장르별 영화들의 UI를 보여주는 컴포넌트로 영화의 정보를 props를 통해 전달받는다.

Movie.jsx

const Movie = (props) => {
return(
  <div className="movie">
      <img src={`https://image.tmdb.org/t/p/original/${props.props.backdrop_path}`} alt={props.props.name}/>
  </div>
  )

image.png

Movie 컴포넌트에는 각 렌더링된 영화마다 Modal이 띄워지고 Modal에도 props를 통해 데이터를 전달할 수 있도록 작업할 예정이다.

/Containers


상태를 갖고 있고, 비동기 작업을 수행하면서 계속적으로 상태가 변경될 수 있다. 상태를 Redux 안에서 관리하기 때문에 Presentation Component는 Redux와 직접 연결되는 부분이 없으므로 Container Component가 React 세계를 Redux와 연결하는 역할을 하는 컨테이너를 담을 폴더이다.

image.png

각 장르들에 대한 action을 dispatch해 컴포넌트에 데이터를 전달해 UI로 보여줄 수 있도록 action별로 파일을 만들었고 Layout폴더에서는 배치될 순서대로 파일을 하나로 정리해주려고한다.

아래는 액션영화의 데이터를 가져오는 파일을 예시로 다른 장르 Container 파일들도 각 action에 맞게 dispatch 해준다.

ActionContainer.jsx

import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchActionMovies } from '../store/actions/index';
import Movie from '../components/Movie';

const ActionContainer = (props) => {

    const dispatch = useDispatch();

    useEffect(() => {
        dispatch(fetchActionMovies());
    }, []);

    const actionData = useSelector(state => state.action.movies, []) || [];

    return (
        <div>
            <p>Action Movies</p>
            <div className="movieContainer">
                { actionData.results && actionData.results.map(movie => (
                    <Movie props={movie} key={movie.id}/>
                ))}
            </div>
        </div>
    )
}

export default ActionContainer;

Api연동은 Redux를 통해 관리하고 Data를 가져오는 액션을 디스패치해 Movie컴포넌트에 해당 영화 정보를 props를 통해 전달해줬다. Redux 적용과정은 이전 시리즈에서 볼 수 있다.

Layout.jsx

import React from 'react';
import NetflixOriginalContainer from './NetflixOriginalContainer';
import TrendContainer from './TrendContainer';
import TopRatedContainer from './TopRatedContainer';
import ActionContainer from './ActionContainer';
import ComedyContainer from './ComedyContainer';
import HorrorContainer from './HorrorContainer';
import RomanceContainer from './RomanceContainer';

const Layout = () => {

    return (
        <div className="layout">
            <NetflixOriginalContainer/>
            <ActionContainer/>
            <TrendContainer/>
            <TopRatedContainer/>
            <ComedyContainer/>
            <RomanceContainer/>
            <HorrorContainer/>
        </div>
    )
}

장르별로 렌더링한 영화들을 순서에 맞춰 레이아웃을 지정해주고 App.js에서 불러와 사용한다.

/static

웹 사이트 구성요소 중 Image, CSS, Script파일과 같이 그 내용이 고정되어 응답을 할 때 별도의 처리 없이 파일 내용을 그대로 보내주면 되는 파일을 의미한다.

image.png

필요한 로고나 배경 이미지, 아이콘 등을 담을 images 폴더와
스타일링을 담당하는 sass 폴더에는 추상으로 번역되며 scss의 기능별로 파일을 만들어 재사용성을 높혀주고 코드가 간결해지는 장점이 있는 /abstracts 폴더와 컴포넌트들의 스타일링을 지정하는 /components 폴더, index의 역할을 하는 style.scss파일로 구성했다.

/Store

Redux의 action과 reducer를 담을 폴더다.

image.png

actions 폴더에는 index.js에 액션 타입정의와 액션 생성함수를 한 파일에 작성했다.

액션 타입

export const FETCH_TRENDING = 'FETCH_TRENDING';
export const FETCH_NETFLIX_ORIGINALS = 'FETCH_NETFLIX_ORIGINALS';
export const FETCH_TOP_RATED = 'FETCH_TOP_RATED';
export const FETCH_ACTION_MOVIES = 'FETCH_ACTION_MOVIES';
export const FETCH_COMEDY_MOVIES = 'FETCH_COMEDY_MOVIES';
export const FETCH_HORROR_MOVIES = 'FETCH_HORROR_MOVIES';
export const FETCH_ROMANCE_MOVIES = 'FETCH_ROMANCE_MOVIES';
export const FETCH_DOCUMENTARIES = 'FETCH_DOCUMENTARIES';

액션 생성함수

const API_KEY = '224ce27b38a3805ecf6f6c36eb3ba9d0';
const BASE_URL = `https://api.themoviedb.org/3`

export const fetchTrendData = (data) => {
  return {
      type: FETCH_TRENDING,
      data
  }
}

export const fetchTrending = () => {
  return (dispatch) => {
      return axios.get(`${BASE_URL}/trending/all/week?api_key=${API_KEY}&language=en-US`)
          .then(response => {
              dispatch(fetchTrendData(response.data))
          })
          .catch(error => {
              throw(error);
          });
  }
}

Trending Data를 예시로 보여줬고 다른 장르들을 가져오는 액션함수들도 작성해줬다.

마무리


컴포넌트를 만들면서 점점 넷플릭스의 형태를 갖춰가는 모습이 보여 재미있게 작업을 할 수 있었다.
props를 전달하는 과정에서 시간을 많이 소비했고 리액트에 대해 더 공부를 해야될 필요성을 느꼈고, 프로젝트를 진행하면서 과정을 설명하는 일이 생각보다 어려워서 미루다가 작성하는데 앞으로는 더 자세한 설명을 위해 바로 글을 쓰는 습관을 길러야겠다.

다음 포스팅 - style