React 일회용 앱 2: 영화 검색 앱

41056 단어 webdevbeginnersreact
첫 번째 에서는 시리즈의 목표를 소개했고 통화 변환기를 만들었습니다. 여기에서는 영화 검색 앱을 빌드하게 됩니다.

규칙(상기시키기 위해)


  • 앱이 60분 이내에 완료되어야 합니다(복잡도에 따라 다름).
  • 순수 React여야 합니다(react-router 또는 redux 없음).
  • 일주일 후에 프로젝트를 삭제해야 합니다. 왜요? 이들은 언제든지 구축할 수 있어야 하며 진지한 취업 면접을 위한 포트폴리오로 보여줄 가치가 없는 기본 앱입니다.
  • 디자인에 많은 시간을 할애하지 마십시오. 아이디어는 React에서 생각하는지 확인하는 것입니다. 60분 후 취향에 맞게 스타일링 할 수 있습니다.
  • 자신의 솔루션을 완료할 때까지 내 솔루션을 보지 마십시오. 그렇지 않으면 5년 동안 '튜토리얼 연옥'에 시달릴 것입니다
  • .

    앱 2 - 영화 검색 앱


  • 외부 API에 연결하는 영화 앱을 빌드합니다.
  • 영화 영화를 검색하고 표시할 영화를 선택합니다.
  • 지속 시간은 1 - 2시간 이내여야 합니다(스타일링 포함).

  • 다음은 여러분이 빌드하기를 기대하는 스크린샷입니다.



    이 앱은 다음 방법을 이해하고 있음을 보여줍니다.
  • 구성 요소 및 상태 작동
  • api에서 데이터 요청
  • 구성 요소 수명 주기 방법
  • 이벤트 사용
  • 상태 변경에 따라 UI 업데이트

  • 당신의 시간이 지금 시작됩니다! 작업을 완료할 때까지 내 솔루션을 보지 마십시오.

    내 솔루션



    OMDb API을 사용하여 동영상 데이터를 가져왔습니다. API 키를 받아야 합니다(무료입니다). PostMan에서 다양한 요청을 가지고 놀면서 API에 익숙해져야 했기 때문에 이 작업을 완료하는 데 60분 이상을 보냈다고 고백해야 합니다. 항상 그렇듯이 저는 create-react-app을 사용하여 프로젝트를 생성했습니다.

    내 앱을 구성하려면 컨테이너와 구성 요소를 결정해야 했습니다.



    내 폴더 구조는 다음과 같습니다.


    MovieCard.js:

    이 구성 요소는 선택한 동영상을 표시하는 데 사용됩니다. 소품을 통해 동영상 데이터를 받습니다.

    import React from 'react';
    
    import './MovieCard.css';
    
    const MovieCard = (props) => {
        return (
            <div className="container">
                <div className="movie-card">
                    <div className="movie-header" style={{ backgroundImage: `url(${props.movie.Poster})` }}>
                    </div>
                    <div className="movie-content">
                        <div className="movie-content-header">
                            <h3 className="movie-title">{props.movie.Title}</h3>
                        </div>
                        <div className="movie-info">
                            <div className="info-section">
                                <label>Released</label>
                                <span>{props.movie.Released}</span>
                            </div>
                            <div className="info-section">
                                <label>IMDB Rating</label>
                                <span>{props.movie.imdbRating}</span>
                            </div>
                            <div className="info-section">
                                <label>Rated</label>
                                <span>{props.movie.Rated}</span>
                            </div>
                            <div className="info-section">
                                <label>Runtime</label>
                                <span>{props.movie.Runtime}</span>
                            </div>
                        </div>
                        <div className="plot" style={{fontSize: '12px'}}>
                            <p>{props.movie.Plot}</p>
                        </div>
                    </div>
                </div>
            </div>
        );
    };
    
    export default MovieCard;
    


    MovieCard.css:

    
    .container {
        display: flex;
        flex-wrap: wrap;
        max-width: 100%;
        margin-left: auto;
        margin-right: auto;
        justify-content: center;
    }
    
    .movie-card {
        background: #ffffff;
        box-shadow: 0px 6px 18px rgba(0,0,0,.1);
        width: 100%;
        max-width: 290px;
        margin: 2em;
        border-radius: 10px;
        display:inline-block;
        z-index: 10;
    }
    
    .movie-header {
        padding:0;
        margin: 0;
        height: 434px;
        width: 100%;
        display: block;
        border-top-left-radius: 10px;
        border-top-right-radius:10px;
        background-size: cover;
    }
    
    .movie-content {
        padding: 18px 18px 24px 18px;
        margin: 0;
    }
    
    .movie-content-header, .movie-info {
        display: table;
        width: 100%;
    }
    
    .movie-title {
        font-size: 24px;
        margin: 0;
        display: table-cell;
        cursor: pointer;
    }
    
    .movie-title:hover {
        color:rgb(228, 194, 42);
    }
    
    .movie-info {
        margin-top: 1em;
    }
    
    .info-section {
        display: table-cell;
        text-transform: uppercase;
        text-align:center;
    }
    
    .info-section:first-of-type {
        text-align:left;
    }
    
    .info-section:last-of-type {
        text-align:right;
    }
    
    .info-section label {
        display: block;
        color: rgba(0,0,0,.5);
        margin-bottom: .5em;
        font-size: 9px;
    }
    
    .info-section span {
        font-weight: 700;
        font-size: 11px;
    }
    
    @media only screen and (max-width: 400px) {
        .movie-header {
            height: 400px;
        }
    }
    


    Search.js

    다음으로 검색 입력과 반환된 결과 목록을 포함하는 검색 구성 요소가 있습니다.
    다음은 Search.js입니다.

    import React from 'react';
    
    import './Search.css';
    
    const Search = (props) => {
        let resultList = null
    
        if (props.searching && (props.defaultTitle !== '')) {
            resultList = (
                <ul className="results">
                    {props.results.map(item => (
                        <li key={item.imdbID} onClick={() => props.clicked(item)}>
                            <img src={item.Poster} alt="Movie Poster"/>
                            {item.Title}
                        </li>
                    ))}
                </ul>
            )
        }
    
        return (
            <div className="search">
                <input type="search" name="movie-search" value={props.defaultTitle} onChange={props.search} />
                {resultList}
            </div>
        );
    };
    
    export default Search;
    
    


    검색.css

    .search {
        position: relative;
        margin: 0 auto;
        width: 300px;
        margin-top: 10px;
    }
    
    .search input {
        height: 26px;
        width: 100%;
        padding: 0 12px 0 25px;
        background: white;
        border: 1px solid #babdcc;
        border-radius: 13px;
        box-sizing: border-box;
        box-shadow: inset 0 1px #e5e7ed, 0 1px 0 #fcfcfc;
    }
    
    .search input:focus {
        outline: none;
        border-color: #66b1ee;
        box-shadow: 0 0 2px rgba(85, 168, 236, 0.9);
    }
    
    
    .search .results {
        display: block;
        position: absolute;
        top: 35px;
        left: 0;
        right: 0;
        z-index: 20;
        padding: 0;
        margin: 0;
        border-width: 1px;
        border-style: solid;
        border-color: #cbcfe2 #c8cee7 #c4c7d7;
        border-radius: 3px;
        background-color: #fdfdfd;
    }
    
    .search .results li { 
        display: flex;
        align-items: center;
        padding: 5px;
        border-bottom: 1px solid rgba(88, 85, 85, 0.3);
        text-align: left;
        height: 50px;
        cursor: pointer;
    }
    
    .search .results li img { 
        width: 30px;
        margin-right: 5px;
    }
    
    .search .results li:hover { 
        background: rgba(88, 85, 85, 0.1);
    }
    


    MovieSearch.js

    모든 상태를 관리하고 소품을 통해 다른 구성 요소에 데이터를 전달하고 싶기 때문에 MovieSearch를 상태 저장 구성 요소로 만들었습니다. 먼저 omdb api에서 api 키를 얻었는지 확인하십시오.
    내 MovieSearch.js 컨테이너는 다음과 같습니다.

    import React, { Component } from 'react';
    import axios from 'axios';
    
    import MovieCard from '../../components/MovieCard/MovieCard';
    import Search from '../../components/Search/Search';
    
    class MovieSearch extends Component {
        state = {
            movieId: 'tt1442449', // default imdb id (Spartacus)
            title: '',
            movie: {},
            searchResults: [],
            isSearching: false,
        }
    
        componentDidMount() {
            this.loadMovie()
        }
    
        componentDidUpdate(prevProps, prevState) {
            if (prevState.movieId !== this.state.movieId) {
                this.loadMovie()
            }
        }
    
        loadMovie() {
            axios.get(`http://www.omdbapi.com/?apikey=YOUR_API_KEY&i=${this.state.movieId}`)
                .then(response => {
                    this.setState({ movie: response.data });
                })
                .catch(error => {
                    console.log('Opps!', error.message);
                })
        }
    
        // we use a timeout to prevent the api request to fire immediately as we type
        timeout = null;
    
        searchMovie = (event) => {
            this.setState({ title: event.target.value, isSearching: true })
    
            clearTimeout(this.timeout);
    
            this.timeout = setTimeout(() => {
                axios.get(`http://www.omdbapi.com/?apikey=YOUR_API_KEY&s=${this.state.title}`)
                    .then(response => {
    
                        if (response.data.Search) {
                            const movies = response.data.Search.slice(0, 5);
                            this.setState({ searchResults: movies });
                        }
                    })
                    .catch(error => {
                        console.log('Opps!', error.message);
                    })
            }, 1000)
    
    
        }
    
        // event handler for a search result item that is clicked
        itemClicked = (item) => {
            this.setState(
                {
                    movieId: item.imdbID,
                    isSearching: false,
                    title: item.Title,
                }
            )
        }
    
    
        render() {
            return (
                <div onClick={() => this.setState({ isSearching: false })}>
                    <Search
                        defaultTitle={this.state.title}
                        search={this.searchMovie}
                        results={this.state.searchResults}
                        clicked={this.itemClicked}
                        searching={this.state.isSearching} />
    
                    <MovieCard movie={this.state.movie} />
                </div>
            );
        }
    }
    
    export default MovieSearch;
    


    이 컨테이너는 상태를 처리하고 애플리케이션의 변경 사항을 업데이트하는 데 사용됩니다.
    위의 코드는 단순히 초기 무비를 마운트할 때 로드합니다. movieId 상태를 검색하고 업데이트할 때마다 소품을 통해 MovieCard의 콘텐츠를 업데이트합니다.

    결론



    조금 급하게 만들었다고 생각할 수 있습니다. 이것은 튜토리얼이 아니라 React에서 생각할 수 있다고 느끼는 초보자를 위한 도전임을 기억하십시오. 내 코드는 단지 가이드일 뿐입니다. 읽어주셔서 감사하고 다음 편에서 뵙기를 바랍니다.

    이건 버리지 않을 것 같아요 ;)

    1부 링크:

    좋은 웹페이지 즐겨찾기