[리액트]따라하며 배우는 노드, 리액트 시리즈 - 영화 사이트 만들기1
본문 바로가기

React & React Native

[리액트]따라하며 배우는 노드, 리액트 시리즈 - 영화 사이트 만들기1

728x90
반응형

1강부터 8강까지 학습목표

  • 다양한 API를 활용하는 법을 배운다.

 

1. 먼저 깃헙에서 보일러플레이트가 되는 압축 파일을 받거나 클론을 해준다.

https://github.com/jaewonhimnae/boilerplate-mern-stack

 

jaewonhimnae/boilerplate-mern-stack

Boilerplate when you use REACT JS, MONG DB, EXPRESS JS, REDUX - jaewonhimnae/boilerplate-mern-stack

github.com

 

server → config → keys.js 에는 분기처리를 해준다. 

if (process.env.NODE_ENV === 'production') {
    module.exports = require('./prod'); /* deploy 이후 프로덕션 모드 */
} else {
    module.exports = require('./dev'); /* local 개발환경 */
}

server → config → prod.js 에서는 프로덕션 모드로 아래 변수를 읽어준다.

module.exports = {
    mongoURI:process.env.MONGO_URI
}

server → config → dev.js 에서는 로컬 개발 환경으로 MongoDB와 연결

module.exports = {
    mongoURI:'mongodb+srv://Sohyun:<password>@cluster0.zbzxd.mongodb.net/myFirstDatabase?retryWrites=true&w=majority'
}

몽고DB란?

→ 대표적인 NoSQL "Non Relational Operation Database SQL"의 줄임말 "관계형 데이터베이스가 아닌 SQL"

일반적인 관계형 데이터베이스에서는 데이터의 중복을 제거하고 무결성을 보장하기 위해서 정규화를 하게 되는데

이러한 정규화가 과도한 JOIN으로 인해 성능 저하가 있을 수 있습니다.


2. Landing Page 만들기

STEP-1. 전체적인 Template을 간단하게 만들기

LandingPage.js

import React from 'react'
import { FaCode } from "react-icons/fa";

function LandingPage() {
    return (
        <div style={{ width: '100%', margin: '0' }}>

            { /* Main Image */ }

            <div stype={{ width: '85%', margin: '1rem auto'}}>

                <h2> Movies by latetest</h2>
                <hr />

                {/* Movie Grid Cards */} 
            </div>

            <div style={{ display: 'flex', justifyContent: 'center' }}>
                <button>Load More</button>
            </div>

        </div>
    )
}

export default LandingPage

2. Movie API에서 가져온 모든 데이터를 STATE에 넣기

2가지 중요한 개념

Effect Hook

상태와 같은 리액트 기능들을 클래스를 표현하지 않고 표현할 수 있게 해준다.

import React, { useState, useEffect } from 'react';

function Example() {
	const [count, setCount] = useState(0);
    
    // componentDidMount와 componentDidUpdate와 비슷함
    useEffect(() => {
    	// 브라우저 API를 써 document title를 업데이트 해준다.
        document.title = `You clicked ${count} times`;
        });
        
        return (
        	<div>
            	<p> You clicked {count} times </p>
                <button onClick={() => setCount(count + 1)}>
                	Click me
                </button>
            </div>
           );
          }
이펙트(Side-Effect)의 일반적인 예는 데이터 페칭이나 서브스크립션 설정
그리고 리액트 컴포넌트에서 DOM을 수동으로 변경하는 것이 있다.

3. MainImage Component를 만들기

import React, {useEffect, useState} from 'react'
import { FaCode } from "react-icons/fa";
import { API_URL, API_KEY, IMAGE_BASE_URL } from '../../Config'
import MainImage from './Sections/MainImage'

function LandingPage() {
    
    const [Movies, setMovies] = useState([]);
    const [MainMovieImage, setMainMovieImage] = useState(null);

    /* API_URL과 api_key는 Config에서 상수처리를 해둠 */

    /* MainImage.js의 props를 통해서 이미 정보를 가져올 수 있다. */
    useEffect(() => {
        const endpoint = `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=1`;
        
        fetch(endpoint)
            .then(response => response.json())
            .then(response => {
                setMovies([response.results])
                setMainMovieImage(response.results)
            })
    }, [])

    return (
        <div style={{ width: '100%', margin: '0' }}>

            { /* Main Image */ }
            <MainImage image={`${IMAGE_BASE_URL}w1280${MainMovieImage.backdrop_path}`} />

            <div stype={{ width: '85%', margin: '1rem auto'}}>

                <h2> Movies by latetest</h2>
                <hr />

                {/* Movie Grid Cards */} 
            </div>

            <div style={{ display: 'flex', justifyContent: 'center' }}>
                <button>Load More</button>
            </div>

        </div>
    )
}

export default LandingPage

위 예시에서는 아래와 같은 에러가 나는데,

 

MainMovieImage.backdrop_path에서 MainMovieImage의 정보를 가져오기 전에 렌더링이 되어버려서 나는 에러

이때 조건처리를 해주어 다시 렌더링 해준다.

            {MainMovieImage && 
                <MainImage image={`${IMAGE_BASE_URL}w1280${MainMovieImage.backdrop_path}`} />
            }

4. Gird Card Component 만들기

GridCard 컴포넌트를 가져와 나머지 카드들을 넣어주는 데, 

이미지가 있는 영화의 경우 movie.poster_path로 없는 경우에는 `${IMAGE_BASE_URL}`로 move.poster_path를 잡아준다.

{Movies && Movies.map((movie, index) => (
                    <React.Fragment key={index}>
                        <GridCards 
                            image={movie.poster_path ?
                                `${IMAGE_BASE_URL}w500${movie.poster_path}` : null}
                        />
                    </React.Fragment>
                ))}

5. Load More Function 만들기

button onClick시 동작할 함수를 만들어주는데, 같은 동작을 하는 함수를 두 개 만들기 보다는 하나로 묶어준다.

    useEffect(() => {

        const endpoint = `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=1`;
        
        fetch(endpoint)
            .then(response => response.json())
            .then(response => {
                console.log(response.results)
                setMovies([...response.results])
                setMainMovieImage(response.results[0])
            })
    }, [])

    const loadMoreItems = () => {
        
        const endpoint = `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=2`;
        
        fetch(endpoint)
            .then(response => response.json())
            .then(response => {
                console.log(response.results)
                setMovies([...response.results])
                setMainMovieImage(response.results[0])
            })
    }

수정 후 >

    useEffect(() => {

        const endpoint = `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=1`;
        fetchMovies(endpoint)

    }, [])

    const fetchMovies = (endpoint) => {
        fetch(endpoint)
                .then(response => response.json())
                .then(response => {
                    console.log(response.results)
                    setMovies([...Movies, ...response.results])
                    setMainMovieImage(response.results[0])
                    setCurrentPage(response.page)
                })
    }
    
    const loadMoreItems = () => {
        
        const endpoint = `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=${CurrentPage + 1}`;
        fetchMovies(endpoint)
        
    }

1. 특정 영화에 해당하는 자세한 정보를 가져오기

App.js에 MovieDetail 페이지를 등록해주면서, Route exact path="/movie/:movieId" /> 경로 잡아주기

→ http://localhost:3000/movie/508943

2. 무비 API에서 가져온 정보를 State에다가 집어 넣기

또 다른 hooks인 useState는 상태를 관리해준다.

초기값이 0인지 null인지 선언할 수 있다.

import React, { useState } from 'react';

function Counter(){
    const [number, setNumber] = useState(0);

3. 전체적인 Template 간단히 만들기

import React from 'react'
import { Descriptions, Badge } from 'antd'

function MovieInfo(props) {

    let {movie} = props;

    return (
        <Descriptions title="Movie Info" bordered>
            <Descriptions.Item label="Title">{movie.original_title}</Descriptions.Item>
            <Descriptions.Item label="release_date">{movie.release_date}</Descriptions.Item>
            <Descriptions.Item label="revenue">{movie.revenue}</Descriptions.Item>
            <Descriptions.Item label="runtime">{movie.runtime}</Descriptions.Item>
            <Descriptions.Item label="vote_average" span={2}>
                {movie.vote_average}
            </Descriptions.Item>
            <Descriptions.Item label="vote_count">{movie.vote_count}</Descriptions.Item>
            <Descriptions.Item label="status">{movie.status}</Descriptions.Item>
            <Descriptions.Item label="popularity">{movie.popularity}</Descriptions.Item>
        </Descriptions>
    )
    
}

export default MovieInfo

4. 영화에 나오는 Crews Information를 가져오기

5. 가져온 Crew 정보를 State에 넣기 

import React, {useEffect, useState} from 'react'
import {API_URL, API_KEY, IMAGE_BASE_URL} from '../../Config'
import MainImage from '../LandingPage/Sections/MainImage'
import MovieInfo from './Sections/MovieInfo'
import GridCards from '../commons/GridCards'
import {Row} from 'antd'

export default function MovieDetail(props) {

    let movieId = props.match.params.movieId
    const [Movie, setMovie] = useState([])
    const [Casts, setCasts] = useState([])
    const [ActorToggle, setActorToggle] = useState(false)

    useEffect(() => {
        
        let endpointCrew = `${API_URL}movie/${movieId}/credits?api_key=${API_KEY}`
        let endpointInfo = `${API_URL}movie/${movieId}?api_key=${API_KEY}`
        
        fetch(endpointInfo)
            .then(response => response.json())
            .then(response => {
                console.log(response)
                setMovie(response)
            })

        fetch(endpointCrew)
            .then(response => response.json())
            .then(response => {
                setCasts(response.cast)
            })

    }, [])

    const toggleActorView = () => {
        setActorToggle(!ActorToggle)
    }


    return (
        <div>
        {/* Header */}

        <MainImage 
            image={`${IMAGE_BASE_URL}w1280${Movie.backdrop_path}`} 
            title={Movie.original_title}
            text={Movie.overview}
        />
        
        {/* Body */}
        <div style={{width: '85%', margin: '1rem auto'}}>


        {/* Movie Info */}
        <MovieInfo />

        <br />

        {/* Actors Grid */}
        <div style={{ display: 'flex', justifyContent: 'center', margin: '2rem' }}>
            <button onClick={toggleActorView}> Toggle Actor View </button>
        </div>

        {/* Movie Grid Cards */} 
        {ActorToggle && 
            <Row gutter={[16, 16]}>
                {Casts && Casts.map((cast, index) => (
                    <React.Fragment key={index}>
                        <GridCards 
                            image={cast.profile_path ?
                                `${IMAGE_BASE_URL}w500${cast.profile_path}` : null}
                            characterName={cast.name}
                        />
                    </React.Fragment>
                ))}
            </Row>
            }
        </div>
            
        </div>
    )
}

6. State에 보관해 Data들을 화면에 보여주기

반응형