๐ป ์ด๋ฒ ์คํ๋ฆฐํธ๋ฅผ ๊ฐ์ฅ ์ฆ๊ฒ๊ฒ ๋ชฐ์ ํ๋ค. ํญ์ ๊ทธ๋ฌํ๋ฏ, ์ฒ์์๋ ์ ๋ง ๋ง๋งํ๋ค. ํ์ง๋ง ์ฒ์์ผ๋ก API๋ ์ ์ฉํด๋ณด๊ณ ๊ณ์ ์๋๋ ๊ฒ์ด ๊ฒฐ๊ตญ ๋ ๋, '์ญ์ ํ๋ฉด ๋๋๊ตฌ๋'๋ฅผ ๋ ํ๋ฒ ๋๋ผ๊ฒ ํด์ค ๊ณ ๋ง์ด ์คํ๋ฆฐํธ์๋ค.
๋จผ์ Google's YouTube API์ ์ ์ํ๊ณ
ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์๋ค.
YouTube Data API v3
๋ฅผ ์ ํํ๋ค. (that provides access to YouTube data, such as videos, playli...๋ผ๊ณ ์จ ์๊ธธ๋)
API
ํธ์ถ ์์น๋ ์น๋ธ๋ผ์ฐ์ (์๋ฐ์คํฌ๋ฆฝํธ), ๋ฐ์ดํฐ ์ ํ์ public data
๋ฅผ ์ ํํ๋ค.
API Key
๋ฅผ ๋ฐ์๋ค!
์ด์ API
๋ฅผ ๊ฒ์ํ๋ฉด ์ ์ฌ์ง๊ณผ ๊ฐ์ ํ๋ฉด์ด ๋์ค๊ฒ ๋๋ค. ๊ทธ๋ผ ์ฑ๊ณต์ด๋ค. API key
๋ฅผ ํ๋ก์ ํธ ๋ด์ youtube.js
ํ์ผ์ ๋ง๋ค์ด ๋ณ์์ ๋ด์๋์๋ค.
์์งํ ํฌํผ ํจ์๋ฅผ ์ด๋ป๊ฒ ๋ง๋ค์ด์ผ ํ ์ง ๊ฐ์ด ์กํ์ง ์์์ ์ด ๋ถ๋ถ์ ์๊ฐ์ด ์ ๋ง ๋ง์ด ์์๋๋ค. YouTube
๋์์์ ๊ฒ์ํ๋ ค๋ฉด API
์ Search:lit ์๋ํฌ์ธํธ๋ฅผ ์ฌ์ฉํ๋ค. ๋ณด๋ค ์ฒด๊ณ์ ์ผ๋ก ์ฝ๋๋ฅผ ๊ด๋ฆฌํ๋ ค๋ฉด, ์๋ํฌ์ธํธ์ ์ํธ์์ฉํ๋ ํฌํผ ํจ์๋ฅผ ์์ฑํ๋ ๊ฒ์ด ์ข๋ค๊ณ ํ๋ค. ์ฒ์์๋ ์ด ๋ง์ด ์ดํด๊ฐ ์ ๋์ง ์์๋ค. ํ์ง๋ง API
๋ฌธ์์ ๊ทธ ๋ต์ด ๋์ ์์๋ค.
ํฌํผ ํจ์๋ฅผ ๋ง๋ค๊ธฐ ์ํด ๋จผ์ API ๋ฌธ์๋ฅผ ์ ์ฌํ ์ดํด๋ดค๋ค.
์ผ๋จ HTTP
์์ฒญ(GET
) URL
์ ์ด๋ ๊ฒ ๋์์๋ค. ๊ทธ๋์ ์ผ๋จ Postman
์ผ๋ก key
๊ฐ์ ์
๋ ฅํ์ฌ GET
์์ฒญ์ ๋ณด๋ด๋ดค๋ค.
์ค.. ์๋ต์ ๋ฐ์๋ค. ์ ๊ธฐํ๋ค. '์, ์๋ต์ ๋ฐ์์ ์ด ์๋ต์ ๊ฐ๊ณตํ๋ ๊ฒ์ด ํฌํผ ํจ์์ ์ญํ ์ด๊ตฌ๋'๋ฅผ ๊นจ๋ฌ์๋ค. ๊ตฌํํด๋ณด์. ์คํ๋ฆฐํธ์์ ํฌํผ ํจ์์ ์กฐ๊ฑด๊ณผ ์๊ตฌ์ฌํญ์ ๋ค์๊ณผ ๊ฐ๋ค.
XHR
, fetch
, jQuery
๋ฌด์์ด๋ ์ข๋ค. Search:list
์๋ํฌ์ธํธ์ GET
์์ฒญ์ ๋ณด๋ด๋ผ. ์ด๋ฒ ์คํ๋ฆฐํธ์๋ XHR
์ ์ ํํ๋ค.
callback
ํจ์๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์์ผ ํ๋ค. ์๋ํฌ์ธํธ์ ํธ์ถ ํ ์คํํ๋ ํจ์์ด๊ณ , ์ด๋ ์ฝ๋ฐฑ์ ์ธ์(argument
)๋ ๋น๋์ค ๋ฐฐ์ด๋ก ๋๊ฒจ์ผ ํ๋ค.
์ต์ ์ ๋ด๋ ๊ฐ์ฒด๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ๋๋ค.
query
: ๊ฒ์์ด, ์ฆ API
๋ฌธ์์ q
๋งค๊ฐ๋ณ์์ ํด๋นํ๋ค.max
: ๊ฐ์ ธ์ฌ ๋์์์ ์ต๋ ๊ฐ์์ด๋ฉฐ, ๊ธฐ๋ณธ๊ฐ์ 5์ด๋ค. ๊ทธ๋์ Postman
์์ ์๋ต์ด 5๊ฐ๊ฐ ์๊ตฌ๋. API
๋ฌธ์์์ maxResults
๋งค๊ฐ๋ณ์์ ํด๋น๋๋ค.
key
: ์ธ์ฆ๋ YouTube APIํค
์
๋๋ค. API
์์ฒญ์ key
๋งค๊ฐ๋ณ์๋ฅผ ํญ์ ํฌํจํด์ผ ํ๋ค.
API
ํธ์ถ ์, ๋ค์ ๋งค๊ฐ๋ณ์๋ฅผ ๋ฐ๋์ ๋๊ฒจ์ฃผ์ด์ผ ํ๋ค.
part
์ ๊ฐ์ snippet
์ผ๋ก ์ง์ ํด์ผ ํ๋ค.
type
์ ๊ฐ์ video
์ผ๋ก ์ง์ ํด์ผ ํ๋ค.
๊ทธ๋ฆฌํ์ฌ, searchYouYube.js
์ ์๋ searchYouTube
ํจ์๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ๋ค. XHR
์ ํตํด GET
์์ฒญ์ ๋ณด๋๊ณ ์์์ Postman
์ ์๋ต์ ๋ณด๊ณ ์ ์ ์๊ฒ ์ง๋ง ์๋ต์ ์ฑ๊ณตํ๋ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ ๊ตฌ์กฐ๋ก ์๋ต ๋ณธ๋ฌธ์ ๋ฐํํ๋ค.
{
"kind": "youtube#searchListResponse",
"etag": etag,
"nextPageToken": string,
"prevPageToken": string,
"pageInfo": {
"totalResults": integer,
"resultsPerPage": integer
},
"items": [
search Resource
]
}
์๋ต ๊ฐ์ฒด์ items
์์ฑ์ ๋น๋์ค ์ ๋ณด๋ค์ด ๋ค์ด์๋ค.
{
"kind": "youtube#searchResult",
"etag": etag,
"id": {
"kind": string,
"videoId": string,
"channelId": string,
"playlistId": string
},
"snippet": {
"publishedAt": datetime,
"channelId": string,
"title": string,
"description": string,
"thumbnails": {
(key): {
"url": string,
"width": unsigned integer,
"height": unsigned integer
}
},
"channelTitle": string
}
}
๊ทธ๋ฌ๋ฏ๋ก, callback
ํจ์์ response.items
๋ฐฐ์ด์ ๋๊ฒผ๋ค.
์ค์๊ฐ ๋ฐ์ดํฐ๋ฅผ ํตํฉํ๊ธฐ ์ํด searchYouTube
์์ ๋ฆฌํดํ๋ ์ค์๊ฐ ๋์์์ ์ด์ฉํด ์ฑ์ ๋ ๋๋ง ํด์ผํ๋ค. React life cycle
์ ์ดํดํด์ผ ํ๋๋ฐ ์๋ ํ๋ฅญํ ๊ทธ๋ฆผ์ด ์๋ค.
componentDidMount()
๋ฅผ ์ด์ฉํ์ฌ ๊ตฌํํ๋ค.
// App.js ์๋จ์ searchYouTube() ํจ์์ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ์ ๋ฌํ ๊ฐ์ฒด๋ฅผ ์ ์ธํ๋ค.
const youTube = {
query: 'parkhyoshin'
max: 10,
key: YOUTUBE_API_KEY,
};
// GET ์์ฒญ์ ํตํ ์๋ต์ ๋ฐฐ์ด๋ก ๋ฐํํ๋ ํจ์(searchYouTube)๋ฅผ ํธ์ถํ๊ณ ,
// callback ํจ์๋ฅผ ํตํด ๋ฐ์ ๋ฐฐ์ด result๋ฅผ setState()์ ํตํด ์ํ์ ์ ์ฉํ๋ค.
componentDidMount() {
searchYouTube(youTube, (result) => {
this.setState({ videos: [...result], current: result[0] });
});
}
์์ ๊ทธ๋ฆผ์ ๋ด๋ ๊ทธ๋ ๊ณ componentDidMount() ๊ณต์ ๋ฌธ์์ ๋ฐ๋ฅด๋ฉด componentDidMount()
๋ ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ๋ ์งํ ํธ์ถ๋๊ณ , ์ธ๋ถ์์ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์์ผ ํ๋ค๋ฉด, ๋คํธ์ํฌ ์์ฒญ์ ๋ณด๋ด๊ธฐ ์ ์ ํ ์์น๋ผ๊ณ ๋์์๋ค.
๋ํ, componentDidMount()
์์ ์ฆ์ setState()
๋ฅผ ํธ์ถํ๋ ๊ฒฝ์ฐ, ์ด๋ก ์ธํ์ฌ ์ถ๊ฐ์ ์ธ ๋ ๋๋ง์ด ๋ฐ์ํ์ง๋ง, ๋ธ๋ผ์ฐ์ ๊ฐ ํ๋ฉด์ ๊ฐฑ์ ํ๊ธฐ ์ ์ ์ด๋ฃจ์ด์ง ๊ฒ์ด๋ค. ์ด ๊ฒฝ์ฐ render()
๊ฐ ๋ ๋ฒ ํธ์ถ๋์ง๋ง, ์ฌ์ฉ์๋ ๊ทธ ์ค๊ฐ ๊ณผ์ ์ ๋ณผ ์ ์์ ๊ฒ์ด๋ผ๊ณ ๋์ ์๋ค. ํ์ง๋ง ์ด๋ฐ ์ฌ์ฉ ๋ฐฉ์์ ์ฑ๋ฅ ๋ฌธ์ ๋ก ์ด์ด์ง๊ธฐ ์ฌ์ฐ๋ฏ๋ก ์ฃผ์๊ฐ ํ์ํ๋ค.
Search
์ปดํฌ๋ํธ๋ฅผ ๊ตฌํํ๊ณ , ๋ค๋ฅธ ์ปดํฌ๋ํธ๋ฅผ ์
๋ฐ์ดํธํ๋ค. <input>
์์์ ์ฌ์ฉ์๊ฐ ๊ฒ์์ด๋ฅผ ์
๋ ฅํ๊ณ , ๊ฒ์ ๋ฒํผ์ ๋๋ฅด๋ฉด ๊ทธ ๋ API
๋ฅผ ์์ฒญํ์ฌ ๊ฒฐ๊ณผ๋ฅผ ํ๋ฉด์ ์
๋ฐ์ดํธํ๋ ํํ๋ก ๊ตฌํํ๋ค. ์ต์ข
์ฝ๋๋ ๋ค์๊ณผ ๊ฐ๋ค.
import React from 'react';
import Nav from './Nav';
import VideoPlayer from './VideoPlayer';
import VideoList from './VideoList';
import { searchYouTube } from '../searchYouTube';
import { YOUTUBE_API_KEY } from '../../config/youtube';
import { fakeData } from './__test__/fakeData';
const youTube = {
query: 'parkhyoshin',
max: 10,
key: YOUTUBE_API_KEY,
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
videos: fakeData,
current: fakeData[0],
};
this.handleSearch = this.handleSearch.bind(this);
this.handleChange = this.handleChange.bind(this);
}
// API๋ฅผ ์์ฒญํ๊ณ ๊ทธ์ ๋ฐ๋ฅธ ์๋ต์ ์ํ์ ๋ฐ์ํ๋ ํจ์
goToSearch() {
searchYouTube(youTube, (result) => {
this.setState({ videos: [...result], current: result[0] });
});
}
// ๊ฒ์ ๋ฒํผ์ ๋๋ฅด๋ฉด ์ต์ข
์ ์ผ๋ก ์คํ๋๋ ํจ์
// ์๋ก์ด ๊ฒ์์ด(newQuery)๋ฅผ ๋ฐ์์ youTube ๊ฐ์ฒด์ ๋ฐ์ํ๊ณ goToSearch ๋ฉ์๋ ์คํ
handleSearch(newQuery) {
youTube.query = newQuery;
this.goToSearch();
}
handleChange(videoKey) {
for (let i = 0; i < this.state.videos.length; i++) {
if (videoKey === this.state.videos[i].id.videoId) {
this.setState({ current: this.state.videos[i] });
break;
}
}
}
componentDidMount() {
this.goToSearch();
}
render() {
return (
<div>
// Nav ์ปดํฌ๋ํธ์ handleSearch ํจ์ ์ ๋ฌ
<Nav search={this.handleSearch} />
<div className="parent">
<VideoPlayer video={this.state.current}></VideoPlayer>
<VideoList clickTitle={this.handleChange} videos={this.state.videos} />
</div>
</div>
);
}
}
export default App;
import React from 'react';
import Search from './Search';
const Nav = (props) => (
<nav className="navbar">
<div className="col-md-6 col-md-offset-3">
// App ์ปดํฌ๋ํธ๋ก๋ถํฐ ์ ๋ฌ๋ฐ์ ํจ์ ๊ทธ๋๋ก Search ์ปดํฌ๋ํธ์๊ฒ ์ ๋ฌ
<Search search={props.search} />
</div>
</nav>
);
export default Nav;
import React from 'react';
const Search = (props) => {
let query = '';
// ๊ฒ์ ๋ฒํผ์ ํด๋ฆญํ๋ ์๊ฐ ์คํ๋๋ ํจ์
// Nav ์ปดํฌ๋ํธ๋ก๋ถํฐ ์ ๋ฌ๋ฐ์ props.search ํจ์ ํธ์ถ
const clickSearch = () => {
props.search(query);
};
// query ๋ณ์์ input์ ๋ด์ฉ์ ๋ด์๋๋๋ค
const getInput = (e) => {
query = e.target.value;
};
return (
<div className="search-bar form-inline">
// input์ ๋ด์ฉ์ด ๋ฐ๋๋๋ง๋ค getInput ํจ์ ํธ์ถ
<input className="form-control" type="text" onChange={getInput} />
// ๋ฒํผ์ ํด๋ฆญํ๋ฉด clickSearch ํจ์ ํธ์ถ
<button className="btn hidden-sm-down" onClick={clickSearch}>
๊ฒ์
</button>
</div>
);
};
export default Search;
searchYouTube
ํจ์์ ์ด๊ธฐ ํ๋ผ๋ฏธํฐ ๊ฐ์ youTube = { query: '์ง๋๋๊ณค', max: 10, key: YOUTUBE_API_KEY };
๋ก ์ค์ ํ๊ณ , ๋ฐํจ์
์ ๊ฒ์ํ ๊ฒฐ๊ณผ์ ๋น๋์ค ์ฌ์๊น์ง ๋ํ๋๋ ํ๋ฉด์ด๋ค.
๋ฟ๋ฏํ๋ค.
์งง๊ฒ๋๋ง ๋๋ ์ ์ ์ ์ด๋ณด์. ์ ๋ง ๋ง์ด ๋ฐฐ์ ๋ค. ์๋กญ๊ฒ ์๊ฒ ๋ ๋ด์ฉ์ด ์ ๋ง ๋ง๊ณ ๋ฆฌ์กํธ์ ์ข ๋ ์ต์ํด์ง ์ ์๋ ์๊ฐ์ด์๋ค. ๋จ์๊ฐ์ ๋ง์ ๊ฐ๋ ์ด ๋จธ๋ฆฟ์์ผ๋ก ๋ค์ด์์ ํผ๋์ค๋ฝ์ง๋ง ์ฐจ๊ทผ์ฐจ๊ทผ ๋ณต์ตํด๋ด์ผ๊ฒ ๋ค.
์ฝ๋๋ฅผ ์ง๊ธ ๋ค์ ๋ด๋ ๊ณ ์น๊ณ ์ถ์ ๋ถ๋ถ(State
์ default
๊ฐ์ fakeData
๋ฅผ ๋ด์๋์ ๊ฒ์ด๋ผ๋ ์ง, ๊ณต์ ๋ฌธ์๊ฐ render()
๋ฅผ ๋ ๋ฒ ํด๋ ์ฌ์ฉ์์ ๋์ ์ ๋ณด์ผ ๊ฒ์ด๋ผ ํ๋๋ฐ ์ ๋ณด์ธ๋ค)์ด ์๋ค. ํ์ง๋ง ๋ฌด์๋ณด๋ค๋ ๋ฟ๋ฏํ ๊ฐ์ ์ ์จ๊ธธ ์๊ฐ ์๋ค. ์ ์ด๋ ๋์๊ฒ๋ ๊ฐ๋ฐ ๊ณผ์ ์ด ๊ฒฐ์ฝ ์ฝ์ง ์์๊ณ , ๊ฒฐ๊ณผ์ ์ผ๋ก ์ญ์ ํ ์ ์๋ค๋ ์์ ๊ฐ์ ์ป์ ๊ฒ ๊ฐ์ ๊ธฐ์๋ค. ์์ผ๋ก๋ ํ๋ฃจํ๋ฃจ ์ฆ๊ฒ๊ฒ ์ฝ๋ฉํด์ผ๊ฒ ๋ค!