Dev

React 공부 정리 #2

August 3, 2016

React 공부 정리 #2

Ajax Requests with React

  • Udemy 강좌의 ‘Section 2: Ajax Requests with React’에 해당하는 내용

index.js를 클래스 기반 컴포넌트로 리팩토링

# index.js
import React, { Component } from 'react';  
import ReactDOM from 'react-dom';  
import YTSearch from 'youtube-api-search';  
import SearchBar from './components/search_bar';

const API_KEY = 'AIzaSyDBv1L6bjiubNh1irNE_CewqdtyfxvGfe8';

class App extends Component {  
  constructor(props) {
    super(props);
    this.state = { videos: [] };  

        YTSearch({key: API_KEY, term: '샤샤샤 직캠'}, (videos) => {
            this.setState({ videos });
            // this.setState({ videos : videos })
        });  
    }

  render() {
    return (
      <div>
        <SearchBar />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.querySelector('.container'));  
  • YouTube API를 사용하기 위해서 npm install --save ytsearch 명령어를 사용해서 라이브러리를 설치
  • 검색어(term)를 사용해서 비디오 객체를 저장하기 위해서 클래스 기반으로 변경하고, 디폴트 상태값을 지정하기 위해서 클래스 생성자 메서드를 사용
  • 리액트는 컴포넌트의 내부 상태를 변경하기 위한 setState 메서드를 제공, 컴포넌트의 UI 상태를 업데이트할 때는 this.setState를 직접 족하는 것이 아니라 항상 setState 메서드를 이용!(몇번을 강조해도 지나침이 없다고 생각되는 내용!)

Props

# index.js
...

import VideoList from './components/video_list';

...

class App extends Component {  
  constructor(props) {
    super(props);
    this.state = { videos: [] };  

        YTSearch({key: API_KEY, term: '샤샤샤 직캠'}, (videos) => {
            this.setState({ videos });
        });  
    }

  render() {
    return (
      <div>
        <SearchBar />
                <VideoList videos={this.state.videos} />
      </div>
    );
  }
}

...

# video_list.js
import React from 'react';

const VideoList = (props) => {  
  return (
    <ul className="col-md-4 list-group">
      {props.videos.length}
    </ul>
  );
};

export default VideoList;  
  • 컴포넌트에서 변하지 않는(immutable) 데이터가 필요 할 땐 사용
  • render() 메소드의 내부에 안에 { this.props.propsName } 형식으로 넣고, 컴포넌트를 사용 할 때 propsName=”value” 를 넣어 값을 설정
  • 단방향으로 데이터가 전송되기 때문에 ‘Parents’ –> ‘Child’로 데이트를 전달, 현재의 예제에선 index.js –> video_list.js로 데이터 전달

Lists map과 Key

# video_list.js
import React from 'react';  
import VideoListItem from './video_list_item';

const VideoList = (props) => {  
    const videoItems = props.videos.map((video) => {
        return <VideoListItem key={video.etag} video={video} />
    });

  return (
    <ul className="col-md-4 list-group">
      {videoItems}
    </ul>
  );
};

export default VideoList;

# video_list_item.js
import React from 'react';

const VideoListItem = ({video}) => {  
    const imageUrl = video.snippet.thumbnails.default.url;

    return (
        <li className="list-group-item">
            <div className="video-list media">
                <div className="media-left">
                    <img className="media-object" src={imageUrl}/>
                </div>
                <div className="media-body">
                    <div className="media-heading">{video.snippet.title}</div>
                </div<
            </div>
        </li>
    );
};

export default VideoListItem;  
  • map 메소드를 사용해서 해당 객체의 결과값을 기준으로 videoItems을 생성자 메서드에서 만듬
  • props를 사용해서 video_list.js –> video_list_item.js 를 호출
  • key를 사용해서 child 컴포넌트를 구분할 수 있도록 video.etag를 사용
  • VideoListItem컴포넌트로 전달된 video props를 사용해서 필요한 내용을 <li> 태그로 만들어서 반환

비디오 세부 정보 출력 컴포넌트 작성

# index.js
import React, { Component } from 'react';  
import ReactDOM from 'react-dom';  
import YTSearch from 'youtube-api-search';  
import SearchBar from './components/search_bar';  
import VideoList from './components/video_list'  
import VideoDetail from './components/video_detail'

const API_KEY = 'AIzaSyDBv1L6bjiubNh1irNE_CewqdtyfxvGfe8';

class App extends Component {  
  constructor(props) {
    super(props);
    this.state = { 
            videos: [],
            selectedVideo: null
        };  

        YTSearch({key: API_KEY, term: '샤샤샤 직캠'}, (videos) => {
            this.setState({ 
                videos: videos,
                selectedVideo: video[0]
            });
        });  
    }

  render() {
    return (
      <div>
        <SearchBar />
                <VideoDetail video={this.state.selectedVideo} />
                <VideoList 
                    onVideoSelect={selectedVideo => this.setState({selectedVideo})}
                    videos={this.state.videos} 
        />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.querySelector('.container'));

# video_detail.js
import React from 'react';

const VideoDetail = ({video}) => {  
  if (!video) {
    return <div>Loading...</div>;
  }

  const videoId = video.id.videoId;
  const url = `https://www.youtube.com/embed/${videoId}`;

  return (
    <div className="video-detail col-md-8">
      <div className="embed-responsive embed-responsive-16by9">
        <iframe className="embed-responsive-item" src={url}></iframe>
      </div>
      <div className="details">
        <div>{video.snippet.title}</div>
        <div>{video.snippet.description}</div>
      </div>
    </div>
  );
};

export default VideoDetail;

# video_list.js
...
const VideoList = (props) => {  
  const videoItems = props.videos.map((video) => {
    return (
      <VideoListItem
        onVideoSelect={props.onVideoSelect}
        key={video.etag}
        video={video} />
    );
  });
...

# video_list_item.js
import React from 'react';

const VideoListItem = ({video, onVideoSelect}) => {  
  const imageUrl = video.snippet.thumbnails.default.url;
  return(
    <li onClick={() => onVideoSelect(video)} className="list-group-item">
      <div className="video-list media">
        <div className="media-left">
          <img className="media-object" src={imageUrl}/>
        </div>
        <div className="media-body">
          <div className="media-heading">{video.snippet.title}</div>
        </div>
      </div>
    </li>
  )
};

export default VideoListItem;  
  • 해당 코드를 수정하는 목적은 출력된 비디오를 클릭하면 화면에 유투브 영상과 세부 내용이 출력되도록 하기 위함
  • selectedVideo는 현재 선택된 유투브 영상을 지칭하며, VideoDetail 컴포넌트를 사용해서 영상과 세부내용을 화면에 출력
  • VideoListItem 컴포넌트에서 onClick 이벤트로 selectedVideo를 선택해서 화면에 출력
  • 처음 실행하면 index.js –> search_bar.js –> video_detail.js –> video_list.js –> video_list_item.js 순서로 컴포넌트가 호출
  • 이후, video_list_item.jsonClick이벤트로 onVideoSelect콜백이 발생하면 video_detail.js –> video_list.js –> video_list_item.js 순서로 호출

검색 지연 기능 추가

# index.js
import _ from 'lodash';  
import React, { Component } from 'react';  
import ReactDOM from 'react-dom';  
import YTSearch from 'youtube-api-search';  
import VideoList from './components/video_list';  
import VideoDetail from './components/video_detail';  
import SearchBar from './components/search_bar';  
const API_KEY = 'AIzaSyDBv1L6bjiubNh1irNE_CewqdtyfxvGfe8';

class App extends Component {  
  constructor(props) {
    super(props);

    this.state = {
      videos: [],
      selectedVideo: null
    };

    this.videoSearch('샤샤샤 직캠');
  }

  videoSearch(term) {
    YTSearch({key: API_KEY, term: term}, (videos) => {
      this.setState({
        videos: videos,
        selectedVideo: videos[0]
      });
    });
  }

  render() {
    const videoSearch = _.debounce((term) => { this.videoSearch(term) }, 300);

    return (
      <div>
        <SearchBar onSearchTermChange={videoSearch} />
        <VideoDetail video={this.state.selectedVideo} />
        <VideoList
          onVideoSelect={selectedVideo => this.setState({selectedVideo}) }
          videos={this.state.videos} />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.querySelector('.container'));

# search_bar.js
import React, { Component } from 'react';

class SearchBar extends React.Component {  
  constructor(props) {
    super(props);
    this.state = { term: ''};
  }

  render() {
    return (
      <div className="search-bar">
        <input
          value={this.state.term}
          onChange={event => this.onInputChange(event.target.value)} />
      </div>
    );
  }

  onInputChange(term) {
    this.setState({term});
    this.props.onSearchTermChange(term);
  }

}

export default SearchBar;  
  • 지연 연산(Lazy Evaluation)을 사용하기 위해서 npm install --save lodash를 설치하고 진행

Styling with CSS

# style.css
.search-bar {
  margin: 20px;
  text-align: center;
}

.search-bar input {
  width: 75%;
}

.video-item img {
  max-width: 64px;
}

.video-detail .details {
  margin-top: 10px;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.list-group-item {
  cursor: pointer;
}

.list-group-item:hover {
  background-color: #eee;
}

# search-bar.js
...
render() {  
    // return <input onChange={this.onInputChange} />;
    return (
        <div className="search-bar">
            <input
                value={this.state.term}
                onChange={event => this.onInputChange(event.target.value)} />
        </div>
    );
}
...
  • CSS를 작성하고 적용할 때, className처럼 낙타 표기법을 사용한다는 점에 주의!
Array