На сегодняшний день я думал, что мы можем создать приложение с помощью React, и поскольку люди из группы Chingu, в которой я работаю, работают над созданием приложения для поиска книг, я решил создать нечто подобное. Я также хотел избежать создания одного и того же приложения, поскольку я бы испортил их, написав эту статью. ^ _ ^

Итак ... Мы собираемся создать приложение для поиска фильмов! : D

Ниже вы можете увидеть дизайн карточки фильма, который мы собираемся использовать в нашем приложении:

Дизайн был вдохновлен этим Dribbble от Rizka Prayuda.

Примечание. В этой статье я не буду вдаваться в подробности того, как работает React (хотя мы будем использовать только базовые концепции). Если вы прочитаете дальше, я предполагаю, что вы уже работали с React раньше. Хотя бы чуть-чуть. ;)

Компонент Movie Card

Перед созданием компонента Movie Card нам нужно поговорить об API, который мы собираемся использовать, потому что он в основном будет определять, как будет выглядеть структура HTML.

В этом примере я использовал OMDb API, так как получить apiKey очень просто, и они дают вам доступ к большому количеству информации о фильмах.

Итак ... после того, как я немного изучил их API, я узнал, что мы можем отправить им идентификатор фильма IMDb в качестве параметра запроса, и они вернут JSON со всеми необходимыми нам данными. В нашем примере мы собираемся извлечь следующие свойства: Заголовок, Сюжет, Плакат, Дата выпуска, список всех жанров и imdbRating, разделенных запятыми.

Ниже вы увидите, как будет выглядеть MovieCard Компонент:

class MovieCard extends React.Component { 
  state = { movieData: {} }; 
  render() { 
    const { 
      Title, 
      Released, 
      Genre, 
      Plot, 
      Poster, 
      imdbRating 
    } = this.state.movieData; 
    if (!Poster || Poster === 'N/A') { return null; } 
    return ( 
      <div className="movie-card-container"> 
        <div className="image-container"> 
          <div className="bg-image" style={{ backgroundImage: `url(${Poster})` }} /> 
        </div> 
        
        <div className="movie-info"> 
          <h2>Movie Details</h2> 
          <div> 
            <h1>{Title}</h1> 
            <small>Released Date: {Released}</small>
          </div> 
          <h4>Rating: {imdbRating} / 10</h4> 
          <p>{Plot && Plot.substr(0, 350)}</p> 
          <div className="tags-container"> 
            {Genre && Genre.split(', ').map(g => ( <span key={g}>{g}</span> ))} 
          </div> 
        </div> 
      </div> 
    ); 
  } 
}

Прежде чем двигаться дальше, я хотел бы объяснить несколько вещей:

  1. Мы устанавливаем state.movieData как пустой объект по умолчанию, чтобы избежать ошибок, когда мы пытаемся получить доступ к его свойствам в методе render. Без этого movieData будет undefined, и это вызовет ошибку.
  2. Если Плакат не имеет значения или имеет значение N/A, мы return null. Это гарантирует, что в этом случае MovieCard ничего не будет отображать. Мы бы не хотели видеть пустую карту, не так ли? :П
  3. Мы устанавливаем Poster как backgroundImage элемента div. Это потому, что мы будем использовать clip-path в CSS, чтобы изображение выглядело закругленным. (Обратите внимание, что Clip-path не работает должным образом во всех браузерах, но если вы используете Chrome, все в порядке;))
  4. Вместо того, чтобы показывать весь Сюжет, я решил разрешить не более 350 символов. Вот почему я использовал на нем .substring(0, 350).
  5. Как я сказал выше, Жанр - это строка, содержащая список всех жанров, разделенных запятыми. Мы split() строку Жанр, используя запятую и пробел: ', '. Это вернет массив, который можно mapped преобразовать в массив отдельных <span> тегов, содержащих соответствующий жанр.

Кроме того, вы могли заметить, что мы используем метод короткого замыкания: Plot && Plot.substr(0, 350). Это гарантирует, что мы вызываем метод .substr() на Plot только в том случае, если Plot не равно undefined. Подробнее об этой технике читайте на MDN.

Вызов API

Пока все хорошо ... У нас есть Компонент, и теперь нам нужно сделать вызов API к конечной точке OMBd и получить нужные нам данные фильма. Для этого я собираюсь использовать Axios, поскольку он дает простой способ делать то, что мы хотим.

Мы добавим вызов axios в жизненный цикл MovieCard componentDidMount.

class MovieCard extends React.Component { 
  state = { movieData: {} }; 
  
  componentDidMount() { 
    axios.get(`https://www.omdbapi.com/?apikey=${your_API}&i=${ this.props.movieID }&plot=full` ) 
      .then(res => res.data) 
      .then(res => { this.setState({ movieData: res }); }); 
  }
  // The rest of the code
}

Axios.get() вернет обещание, содержащее ответ. После этого, вызвав метод setState, мы сохраняем ответ в movieData.

Примечание:

  1. Для успешного вызова конечной точки API вам понадобится apikey. Вы можете получить его на сайте OMDb.
  2. Помимо apiKey мы также передаем идентификатор фильма IMDB в качестве параметра запроса i=. Это значение поступает из this.props, что означает, что когда мы будем использовать <MovieCard>, нам придется передать movieID как опору. Вы поймете, что я имею в виду, в следующем разделе. :)

Компонент MovieList

Этот компонент будет делать следующее:

  1. Укажите form с input, которые будут использоваться пользователем для отправки запроса поиска.
  2. Используя термин search, он сделает запрос к конечной точке OMDb, чтобы получить список всех фильмов, которые содержат соответствующий термин в своих названиях.
  3. Отобразите результаты в виде списка MovieCard.
class MoviesList extends React.Component { 
  state = { moviesList: ['tt2294629'], searchTerm: '' }; 
  search = event => { 
    event.preventDefault(); 
    axios.get( `https://www.omdbapi.com/?apikey=${your_API}&s=${ this.state.searchTerm }&plot=full` ) 
      .then(res => res.data) 
      .then(res => { 
        if (!res.Search) { 
          this.setState({ moviesList: [] });
          return; 
        }
  
        const moviesList = res.Search.map(movie => movie.imdbID);
        this.setState({ moviesList });
    }); 
  }; 
  handleChange = event => { 
    this.setState({ searchTerm: event.target.value }); 
  }; 
  render() { 
    const { moviesList } = this.state; 
    return ( 
      <div> 
        <form onSubmit={this.search}> 
          <input placeholder="Search for a movie" onChange={this.handleChange} /> 
          <button type="submit"> 
            <i className="fa fa-search" /> 
          </button> 
        </form> 
        {moviesList.length > 0 ? ( 
           moviesList.map(movie => ( <MovieCard movieID={movie} key={movie} /> )) 
         ) : ( 
           <p> Couldn't find any movie. Please search again using another search criteria. </p> 
         )} 
      </div> 
    ); 
  } 
}

У нас есть более крупный фрагмент кода выше. Давайте разберемся немного (по крайней мере, «важные» вещи: D):

Имеется два прослушивателя событий.

  1. onChange на input - это вызовет метод handleChange, который будет обновлять состояние searchTerm входным значением каждый раз, когда вход изменяется.
  2. onSubmit на form - это вызовет метод search, который сделает запрос к конечной точке API, предоставив searchTerm в качестве параметра запроса. Он сохранит данные ответа в массив moviesList (Обратите внимание, что мы сохраняем только значения imdbID из каждого возвращенного объекта, потому что это все, что нам нужно передать нашему MovieCard компоненту в качестве опоры).

Тем не менее, мы проверяем, есть ли какие-либо данные в res.Search, в противном случае мы очищаем массив moviesList.

В методе render мы проверяем, есть ли в массиве хотя бы один элемент. Если этого не происходит, мы показываем пользователю сообщение об ошибке.

CSS

На этот раз я не буду вдаваться в подробности всей этой чепухи, которая есть в CSS, так как я не хочу делать сообщение слишком длинным. Но не стесняйтесь проверять код CSS на Github или Codepen.

Вкратце ... Я использовал flexbox несколько раз, чтобы поместить элементы туда, где я хотел. Я также использовал clip-path, чтобы сделать эту округлую форму изображения, и в конце я добавил немного запросов media, чтобы MovieCard выглядело лучше на мобильных устройствах.

В остальном весь код CSS довольно ясен. НО ... если вы хотите, чтобы я рассмотрел это и объяснил более подробно, дайте мне знать, и я обновлю сообщение! ;)

Заключение

Несмотря на то, что часть React не была такой сложной в создании, у меня были некоторые проблемы при попытке разместить все элементы там, где я хотел ... Самая большая проблема из всех заключалась в размещении закругленного изображения, поскольку оно продолжало получать поверх текста справа… и это работает не во всех браузерах, лол. Возможно, это была небольшая трата моего времени, так как мне, возможно, нужно было воссоздать его без использования clip-path. :П

Тем не менее это был интересный проект! (как и большинство личных проектов, которые я создаю, ха-ха).

Вы можете найти его вживую на Codepen.

Дайте мне знать, что вы думаете. Что бы вы добавили, чтобы улучшить его? :)

Первоначально опубликовано на www.florin-pop.com.