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.js의onClick이벤트로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를 설치하고 진행- lodash를 사용한 지연 연산에 관한 내용은 Haruair님의 Lodash의 지연 평가 소개 by Filip Zawada을 참고!
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