[TIL][React] Youtube API 활용 동영상 recast 앱 구현

김태수·2020년 11월 24일
0

React.js

목록 보기
3/3

리액트를 활용하여 Youtube 리캐스트 앱을 구현하였다.

간단한 코멘트 앱에 이어서, 조건에 따라 유튜브 영상을 받와 보여줄 수 있게 구현하였다.
이러한 작업을 하면서 리액트에 대해서 더욱 깊게 알게되었다. 이제는 어느정도 state와 state 끌어올리기, 생명주기 등의 개념에 대해 이해하고 있으며 UI를 동적으로 구성하는데 있어서는 커다란 무리가 없었다!

하지만

유튜브 API를 XHR로 연결하는 부분에서 어려움을 많이 겪었다.. 그간 fetch를 주로 사용했어서 XHR방식을 활용한 서버와의 통신방법에 대해 잊고있었던 부분을 다시 찾아보며 공부하게 되었다..

export const searchYouTube = (optionParams, callback) => {
  let url = 'https://www.googleapis.com/youtube/v3/search?';
  for (let option in optionParams){ //-------- optionParams는 최상단 component의 state에 할당 해 놓았다.
    url+=option+'='+optionParams[option]+'&';
  }
  url = url.slice(0, -1);
  
  const xhr = new XMLHttpRequest()
  xhr.onload = function() {
    if (xhr.status === 200 || xhr.status === 201) {
      callback(xhr.responseText);
    } else {
      console.error(xhr.responseText);
    }
  };
  xhr.open('GET', url);
  xhr.send(); // ------- 실제로 통신을 시작하는 부분
}

위의 코드는 Youtube Data v3 API를 이용해 response를 받아온 뒤 콜백함수를 이용해 body에 특정한 동작을 시키는 함수이다! 이 함수를 구현하여 component의 state를 갱신해 원하는 동영상을 받아올 수 있게 하였다.

가장 큰 문제는 API 할당량 문제였다. 해당 API는 하루에 10000만의 리소스를 제공해 주지만 코드를 잘못 짜서 계속해서 요청을 날리거나, 자주 새로고침 했을 때 1시간도 못가서 할당량을 넘어서 버리게 된다.

때문에 다음과 같은 에러를 수도없이 받았고...ㅠ
이 다음부터는 기본적으로 새로고침을 좀 더 조심스럽게 하게 되었으며,
애초에 request를 좀 더 가볍게 만들기로 하였다!

optionParams: {
        q: '뉴스',
        max: 10,
        key: YOUTUBE_API_KEY,
        part: 'snippet'
      }

처음에는 다음과 같은 파라미터들로 요청을 날렸으나, 이대로 요청을 날렸을 때

이렇게 한개의 동영상에 대한 정보가 엄청나게 날아온다... 물론 이런게 여러개가 한번에 날라온다.
위의 프로젝트에서 한개의 동영상에 대해 필요한 정보는
id/videoID, snippet/thumnails/default/url, snippet/title, snippet/dscription 네개 뿐이다.

그래서 필터링 하여 받아올 수 있는 파라미터를 추가하였다.

optionParams: {
        q: '뉴스',
        max: 10,
        key: YOUTUBE_API_KEY,
        part: 'snippet',
        field: 'items(id(videoId),snippet(title,description,thumbnails(default(url))),'
      }

결과

편-안......

최종적으로..

아래와 같은 앱을 구현하였으며, 이를통해 props drilling의 문제점 또한 알게되었으며
redux 및 react hook의 필요성 또한 느끼게 되었다 ㅠ

Code

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      video: fakeData,
      selectedVideo: fakeData[0],
      optionParams: {
        q: '뉴스',
        max: 10,
        key: YOUTUBE_API_KEY,
        part: 'snippet',
        field: 'items(id(videoId),snippet(title,description),snippet(thumbnails(default(url))))'
      },
      searchKeyWord: ''
    };
    this.handleClick = this.handleClick.bind(this);
    this.getVideoList = this.getVideoList.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.handleSearchClick = this.handleSearchClick.bind(this);
    this.handleKey = this.handleKey.bind(this);
  }
  
  componentDidMount() {
    searchYouTube(this.state.optionParams, this.getVideoList);
  }

  handleKey(e) {
    if(e.key === 'Enter') {
      console.log('wow')
      this.handleSearchClick()
    }
  }
  
  handleClick(e) {
    let target = e.target.textContent
    for(let i of this.state.video) {
      if(i.snippet.title === target) {
        this.setState({selectedVideo: i})
      }
    }
  }

  handleSearchClick() {
    this.setState({optionParams: {
      q: this.state.searchKeyWord,
      max: 10,
      key: YOUTUBE_API_KEY,
      part: 'snippet'
    }})
    searchYouTube(this.state.optionParams, this.getVideoList);
  }

  getVideoList(res) {
    this.setState({video: JSON.parse(res).items});
    this.setState({selectedVideo: this.state.video[0]})
  }

  handleChange(e) {
    this.setState({searchKeyWord: e.target.value});
  }

  render() {
    return (
    <div>
      <Nav handleChange={this.handleChange} handleSearchClick={this.handleSearchClick} handleKey={this.handleKey}/>
      <div className="parent">
        <VideoPlayer video={this.state.selectedVideo}/>
        <VideoList videos={this.state.video} handleClick={this.handleClick}/>
      </div>
    </div>
    )
  }
  
}

//--------------------

const Nav = (props) => (
  <nav className="navbar">
    <div className="col-md-6 col-md-offset-3">
      <Search handleChange={props.handleChange}
      handleSearchClick={props.handleSearchClick}
      handleKey={props.handleKey}/>
    </div>
  </nav>
);

//--------------------

const Search = (props) => (
  <div className="search-bar form-inline">
    <input className="form-control" type="text" onChange={props.handleChange} onKeyPress={props.handleKey}/>
    <button className="btn hidden-sm-down" onClick={props.handleSearchClick}>
      검색
    </button>
  </div>
);

//--------------------

const VideoList = (props) => (
  <div className="video-list media">
    {props.videos.map(video =>
      <VideoListEntry 
      key={video.etag} 
      video={video}
      handleClick={props.handleClick}/>
    )}
  </div>
);

//--------------------

const VideoListEntry = (props) => (
  <div className="video-list-entry">
    <div className="media-left media-middle">
      <img className="media-object" src={props.video.snippet.thumbnails.default.url} alt="" />
    </div>
    <div className="media-body">
      <div className="video-list-entry-title" onClick={props.handleClick}>{props.video.snippet.title}</div>
      <div className="video-list-entry-detail">{props.video.snippet.description}</div>
    </div>
  </div>
);

//--------------------

const VideoPlayer = (props) => (
  <div className="video-player">
    <div className="embed-responsive embed-responsive-16by9">
      <iframe className="embed-responsive-item"
        src={`https://www.youtube.com/embed/${props.video.id.videoId}`} allowFullScreen></iframe>
    </div>
    <div className="video-player-details">
      <h3>{props.video.snippet.title}</h3>
      <div>{props.video.snippet.description}</div>
    </div>
  </div>
);
profile
개발학습 일기

0개의 댓글