์ด๋ฒ ์ฅ์์๋ ์ธ๋ถ API๋ฅผ ์์ฒญํ๊ณ ์๋ต๋ฐ์ ๋ฐ์ดํฐ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ฃผ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋๋ค. API(Application Programming Interface)๋ฅผ ์ฌ์ฉํ๊ธฐ ์์, ์ปดํฌ๋ํธ ์๋ช ์ฃผ๊ธฐ ๋ฉ์๋์ ๋ํด ์์๋ด ๋๋ค. ์์ง API์ ๋ํด ์ ๋ชจ๋ฅด๊ณ ์๋ค๋ฉด, ์ ์๊ฐ ์ด "์๋ฌด๋ ๋์๊ฒ API๋ฅผ ์๋ ค์ฃผ์ง ์์๋ค" ๊ธ์ ๋จผ์ ์ฝ๊ณ ์ค๊ธธ ๋ฐ๋๋๋ค.
ํด์ปค ๋ด์ค(Hacker News)๋ ์์ด ์ฝค๋น๋ค์ดํฐ(Y Combinator, ๋ฏธ๊ตญ ์ค๋ฆฌ์ฝ๋ฐธ๋ฆฌ๋ฅผ ๋ํํ๋ ๊ธ๋ก๋ฒ ์ก์ ๋ฌ๋ ์ดํฐ)๊ฐ ์ด์ํ๋ ๊ธฐ์ ๋ถ์ผ ๋ด์ค ํ๋ ์ด์ ํ๋ซํผ์ ๋๋ค. ํด์ปค ๋ด์ค๋ ๋ฐ์ดํฐ ์กฐํ API์ ๊ฒ์ API๋ฅผ ์ ๊ณตํฉ๋๋ค. ์์ํ๊ธฐ ์ ํด์ปค ๋ด์ค API ๋ช ์ธ์๋ฅผ ์ฝ๊ณ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ํ์ ํด๋ก์๋ค.
2์ฅ์์ ์ฌ์ฉํ ES6 ํด๋์ค ์ปดํฌ๋ํธ ๋ฉ์๋ constructor()
์ render()
๋ ์๋ช
์ฃผ๊ธฐ ๋ฉ์๋(Lifecycle Methods)์
๋๋ค. ์๋ช
์ฃผ๊ธฐ ๋ฉ์๋๋ ES6 ํด๋์ค ์ปดํฌ๋ํธ์์ ์ฌ์ฉํ ์ ์์ง๋ง, ๋น ์ํ ์ปดํฌ๋ํธ์์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ปดํฌ๋ํธ ์ธ์คํด์ค๊ฐ ๋ง๋ค์ด์ ธ DOM์ ์ฝ์
๋ ๋, constructor()
์์ฑ์ ๋ฉ์๋๊ฐ ํธ์ถ๋ฉ๋๋ค. ์ด ๊ณผ์ ์ ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ(mount, ํ์ฌ)๋๋ค๊ณ ํ์ฌ '๋ง์ดํธ ํ๋ก์ธ์ค'๋ผ๊ณ ํฉ๋๋ค.
render()
๋ฉ์๋๋ ๋ง์ดํธ ํ๋ก์ธ์ค ์ค์ ํธ์ถ๋๋ฉฐ, ์ปดํฌ๋ํธ state์ props๊ฐ ๋ณ๊ฒฝ๋์ด ์ปดํฌ๋ํธ๊ฐ ์
๋ฐ์ดํธ ๋ ๋๋ง๋ค ํธ์ถ๋ฉ๋๋ค.
์ด ์ธ์๋ ๋ ๋ง์ ์๋ช ์ฃผ๊ธฐ ๋ฉ์๋๊ฐ ์์ต๋๋ค. ์๋ช ์ฃผ๊ธฐ ํ๋ก์ธ์ค ๋ณ๋ก ํธ์ถ๋๋ ์๋ช ์ฃผ๊ธฐ ๋ฉ์๋์ ๋ํด ์์ธํ ์์๋ณด๊ฒ ์ต๋๋ค.
์ปดํฌ๋ํธ๊ฐ ์ธ์คํด์คํ๋ ๋, '๋ง์ดํธ ํ๋ก์ธ์ค'๊ฐ ์์๋์ด ์๋ ์์๋๋ก ๋ฉ์๋๊ฐ ํธ์ถ๋ฉ๋๋ค.
constructor()
componentWillMount()
render()
componentDidMount()
state๋ props๊ฐ ๋ณ๊ฒฝ ์, '์ ๋ฐ์ดํธ ํ๋ก์ธ์ค'๊ฐ ์์๋ฉ๋๋ค. ์๋ ์์๋๋ก 5๊ฐ์ ์๋ช ์ฃผ๊ธฐ ๋ฉ์๋๊ฐ ํธ์ถ๋ฉ๋๋ค.
componentWillReceiveProps()
shouldComponentUpdate()
componentWillUpdate()
render()
componentDidUpdate()
๋ง์ง๋ง์ผ๋ก ์ปดํฌ๋ํธ ๋ง์ดํธ ํด์ ๋๊ธฐ ์ , componentWillUnmount()
๋ฉ์๋๊ฐ ํธ์ถ๋ฉ๋๋ค.
componentWillUnmount()
์ฒ์๋ถํฐ ๋ชจ๋ ์๋ช
์ฃผ๊ธฐ ๋ฉ์๋๋ฅผ ํ๊บผ๋ฒ์ ์ฌ์ฉํ์ง ์์๋ ๋ฉ๋๋ค. ๋๊ท๋ชจ ์ ํ๋ฆฌ์ผ์ด์
์๋ constructor()
์ render()
๋ฉ์๋๋ง ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. ์์ผ๋ก ์ ์ ์๋ช
์ฃผ๊ธฐ ๋ฉ์๋์ ์ต์ํด์งํ
๋ ๊ฑฑ์ ํ์ง ๋ง์๋ค. ๋ค์์ผ๋ก ์๋ช
์ฃผ๊ธฐ ๋ฉ์๋๋ฅผ ์ธ์ ์ด๋ป๊ฒ ์ฌ์ฉํด์ผ ํ๋์ง ์์๋ด
์๋ค.
-
constructor(props)
๋ฉ์๋๋ ์ปดํฌ๋ํธ ์ด๊ธฐํ ์ ํธ์ถ๋ฉ๋๋ค. ์ด๊ธฐ ์ปดํฌ๋ํธ ์ํ ๋ฐ ํด๋์ค ๋ฉ์๋๋ฅผ ์ ์ํฉ๋๋ค. -
componentWillMount()
๋ฉ์๋๋render()
๋ฉ์๋ ์ ์ ํธ์ถ๋ฉ๋๋ค. ์ปดํฌ๋ํธ ๋ ๋ฒ์งธ ๋ ๋๋ง์ ์ผ์ผํค์ง ์๊ธฐ ๋๋ฌธ์ ์ด ๋ฉ์๋์์ ์ปดํฌ๋ํธ ์ํ๋ฅผ ์ค์ ํ ์ ์์ง๋ง,constructor()
๋ฉ์๋์์ ์ด๊ธฐ ์ํ๊ฐ์ ์ค์ ํ๋ ๊ฒ์ด ๋ฐ๋์งํฉ๋๋ค. -
render()
๋ฉ์๋๋ props ๋ฐ state๋ฅผ ์ฝ๊ณ ์๋ ๋จผํธ๋ฅผ ๋ฐํํฉ๋๋ค. ์ปดํฌ๋ํธ ์ถ๋ ฅ์ ๋ฐํํ๊ธฐ ๋๋ฌธ์ ๋ฐ๋์ ์ฌ์ฉํด์ผ ํฉ๋๋ค. -
componentDidMount()
๋ฉ์๋๋ ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ ๋ ๋ ํ ๋ฒ๋ง ํธ์ถ๋ฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ์ด ๋ฉ์๋์์ ๋น๋๊ธฐ API๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์๋ต๋ฐ์ ์ธ๋ถ ๋ฐ์ดํฐ๋ ๋ด๋ถ ์ปดํฌ๋ํธ ์ํ์ ์ ์ฅ๋์ด ์ปดํฌ๋ํธ๊ฐ ์ ๋ฐ์ดํธ ๋๋ฉดrender()
๋ฉ์๋๊ฐ ์คํ๋ฉ๋๋ค. -
componentWillReceiveProps(nextProps)
๋ฉ์๋๋ ์ ๋ฐ์ดํธ ์๋ช ์ฃผ๊ธฐ์์ ํธ์ถ๋ฉ๋๋ค. ๋ค์ props์nextProps
์ ์ด์ props์ธthis.props
์ ์ฐจ์ด๋ฅผ ๋น๊ตํฉ๋๋ค.nextProps
๋ฅผ ์ปดํฌ๋ํธ state๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค. -
shouldComponentUpdate(nextProps, nextState)
๋ฉ์๋๋ props ๋๋ state๊ฐ ๋ณ๊ฒฝ๋์ด ์ปดํฌ๋ํธ๊ฐ ์ ๋ฐ์ดํธ๋ ๋ ํธ์ถ๋ฉ๋๋ค. ๊ณ ๋ํ๋ ๋ฆฌ์กํธ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฑ๋ฅ ์ต์ ํ๋ฅผ ๊ณ ๋ คํ ๋ ์ฃผ๋ก ์ฌ์ฉ๋ฉ๋๋ค. ๋ฐํ๋๋ ๋ถ์ธ ๊ฐ(boolean)์ ๋ฐ๋ผ ์ปดํฌ๋ํธ์ ๋ชจ๋ ์์์ด ์ ๋ฐ์ดํธ ์ฃผ๊ธฐ ๋์ ๋ ๋๋ง ๋๊ฑฐ๋, ๋ฐ๋๋ก ๋ ๋๋ง๋์ง ์๊ฒ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. ๋ฌธ์ ๊ฐ ๋๋ ํน์ ์ปดํฌ๋ํธ์ ์๋ช ์ฃผ๊ธฐ ๋ ๋๋ง์ ๋ง์ ์ ์์ต๋๋ค. -
componentWillUpdate(nextProps, nextState)
๋ฉ์๋๋render()
๋ฉ์๋ ์ ์ ๋ฐ๋ก ํธ์ถ๋ฉ๋๋ค.nextProps
๋ ๋ค์ props๋ฅผ,nextState
๋ ๋ค์ state๋ฅผ ์กฐํํฉ๋๋ค.render()
๋ฉ์๋๊ฐ ์คํ๋๊ธฐ ์ , ์ํ๋ฅผ ์ ๋ฐ์ดํธํ ์ ์๋ ๋ง์ง๋ง ๊ธฐํ์ ๋๋ค.render()
๋ฉ์๋๊ฐ ์คํ๋ ์ดํ์๋setState()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.nextProps
๋ฅผ state ๊ฐ์ผ๋ก ์ฌ์ฉํ๋ ค๋ฉดcomponentWillReceiveProps()
๋ฉ์๋๋ฅผ ์ฌ์ฉํฉ๋๋ค. -
componentDidUpdate(prevProps, prevState)
๋ฉ์๋๋render()
๋ฉ์๋ ํ์ ์ฆ์ ํธ์ถ๋ฉ๋๋ค. ์ด ๋ฉ์๋์์ DOM ์กฐ์์ ํ๊ฑฐ๋ ์ถ๊ฐ ๋น๋๊ธฐ ์์ฒญ์ ์ํํฉ๋๋ค. -
componentWillUnmount()
๋ฉ์๋๋ ์ปดํฌ๋ํธ ํด์ฒด ์ ์ ํธ์ถ๋ฉ๋๋ค. ์ด ๋ฉ์๋์์ ์ปดํฌ๋ํธ๋ฅผ ์ด๊ธฐํํฉ๋๋ค,
constructor()
์ render()
๋ฉ์๋๋ ์๋ช
์ฃผ๊ธฐ ๋ฉ์๋ ์ค ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ๋๋ ๋ฉ์๋์
๋๋ค. render()
๋ฉ์๋๋ ์ปดํฌ๋ํธ ์ธ์คํด์ค๋ฅผ ๋ฐํํ๊ธฐ ๋๋ฌธ์ ๋ฐ๋์ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
๊ทธ ์ธ componentDidCatch(error, info)
๋ฉ์๋๊ฐ ์์ต๋๋ค. ๋ฆฌ์กํธ ๋ฒ์ 16์์ ์ถ๊ฐ๋ ๋ฉ์๋๋ก ์ปดํฌ๋ํธ ์๋ฌ๋ฅผ ์บ์นํฉ๋๋ค. ์๋ฅผ ๋ค์ด API ํธ์ถ์ด ์คํจํ์ฌ ๋ฆฌ์คํธ ์ํ ๊ฐ์ด null
์ด ๋ ๊ฒฝ์ฐ, filter()
๊ณผ map()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ฌ ์ปดํฌ๋ํธ๊ฐ ๊นจ์ง๋ฉด ์ ํ๋ฆฌ์ผ์ด์
์ ์ฒด๊ฐ ์๋ํ์ง ์๊ฒ ๋ฉ๋๋ค. ์ด๋, componentDidCatch()
๋ก ์ค๋ฅ๋ฅผ ๋ฐ๊ฒฌํ์ฌ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๋ด๋ถ ์ํ์ ์ ์ฅํ๊ณ , ์ด ๋ด์ฉ์ ํ๋ฉด์ ํ์ํ ์ ์์ต๋๋ค.
- [๋ฆฌ์กํธ ๊ณต์ ๋ฌธ์] ๋ฆฌ์กํธ ์๋ช ์ฃผ๊ธฐ
- [๋ฆฌ์กํธ ๊ณต์ ๋ฌธ์] ๋ฆฌ์กํธ ์๋ช ์ฃผ๊ธฐ ๋ฉ์๋์ ์ํ ๊ด๋ฆฌ
- [๋ฆฌ์กํธ ๊ณต์ ๋ฌธ์] ์ปดํฌ๋ํธ ์๋ฌ ํธ๋ค๋ง
์ด๋ฒ ์ ์์๋ ํด์ปค ๋ด์ค API๋ก ์ธ๋ถ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด componentDidMount()
์๋ช
์ฃผ๊ธฐ ๋ฉ์๋ ์์ ์๋ฐ์คํฌ๋ฆฝํธ ๋ค์ดํฐ๋ธ API fetch()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ๊ฒ ์ต๋๋ค.
๋จผ์ ์์ฒญ URL์ ์์(constants)์ ๊ธฐ๋ณธ ๋งค๊ฐ ๋ณ์(default parameters)๋ก ๋ถ์ ํด ๊ตฌ์ฑํฉ๋๋ค.
{title="src/App.js",lang=javascript}
import React, { Component } from 'react';
import './App.css';
# leanpub-start-insert
const DEFAULT_QUERY = 'redux';
const PATH_BASE = 'https://hn.algolia.com/api/v1';
const PATH_SEARCH = '/search';
const PARAM_SEARCH = 'query=';
# leanpub-end-insert
...
ES6๋ ์๋ก์ด ๋ฌธ์์ด ํ๊ธฐ๋ฒ์ธ ํ ํ๋ฆฟ ๋ฆฌํฐ๋ด(template literals)์ ๋์ ํ์ต๋๋ค. ํ ํ๋ฆฟ ๋ฆฌํฐ๋ด์ ์ฌ์ฉํ๋ฉด ์ฌ๋ฌ ์ค๋ก ์ด๋ฃจ์ด์ง ๋ฌธ์์ด์ ์ฝ๊ฒ ํํํ ์ ์์ต๋๋ค. ํ ํ๋ฆฟ ๋ฆฌํฐ๋ด๋ก URL ๊ตฌ์ฑํ์ฌ API ์๋ ํฌ์ธํธ์ ์ฐ๊ฒฐํฉ๋๋ค.
{title="Code Playground",lang="javascript"}
// ES6
const url = `${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${DEFAULT_QUERY}`;
// ES5
var url = PATH_BASE + PATH_SEARCH + '?' + PARAM_SEARCH + DEFAULT_QUERY;
console.log(url);
// ์ถ๋ ฅ: https://hn.algolia.com/api/v1/search?query=redux
์ด๋ฐ ๋ฐฉ๋ฒ์ผ๋ก URL๋ฅผ ๊ตฌ์ฑํ๋ฉด ๊ด๋ฆฌํ๊ธฐ ์ฌ์์ง๋๋ค.
์ด์ API ํธ์ถํ์ฌ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ํ ๋ฒ์ ๊ฐ์ ธ์ต๋๋ค.
{title="src/App.js",lang=javascript}
...
class App extends Component {
constructor(props) {
super(props);
this.state = {
# leanpub-start-insert
result: null,
searchTerm: DEFAULT_QUERY,
# leanpub-end-insert
};
# leanpub-start-insert
this.setSearchTopStories = this.setSearchTopStories.bind(this);
# leanpub-end-insert
this.onSearchChange = this.onSearchChange.bind(this);
this.onDismiss = this.onDismiss.bind(this);
}
# leanpub-start-insert
setSearchTopStories(result) {
this.setState({ result });
}
componentDidMount() {
const { searchTerm } = this.state;
fetch(`${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${searchTerm}`)
.then(response => response.json())
.then(result => this.setSearchTopStories(result))
.catch(error => error);
}
# leanpub-end-insert
...
}
์ด ์ฝ๋ ์์ ์ ๋ง ๋ง์ ์ผ๋ค์ด ์ผ์ด๋ฌ์ต๋๋ค. ๊ฐ ๋จ๊ณ๋ณ๋ก ์ผ์ด๋ ์ผ์ ์ค๋ช ํ๊ฒ ์ต๋๋ค.
์ฒซ์งธ, ํด์ปค ๋ด์ค API๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ๊ธฐ์กด ์ํ ๋ฐ์ดํฐ์ธ list
๋ณ์๋ฅผ ์ญ์ ํ์ต๋๋ค. ์ปดํฌ๋ํธ ๋ด๋ถ ์ด๊ธฐ ์ํ result
๋ null
๋ก ๊ฐ์ด ๋น์ด ์์ผ๋ฉฐ, ๊ฒ์์ด searchTerm
๋ DEFAULT_QUERY
๋ก ์ค์ ๋์ด ์์ต๋๋ค. searchTerm
์ ์ฒซ ๋ฒ์งธ API ์์ฒญํ ๋์ Search ์ปดํฌ๋ํธ์์ ์
๋ ฅ ํ๋ ๊ธฐ๋ณธ ๊ฐ์ ํ์ํ ๋ ์ฌ์ฉ๋ฉ๋๋ค.
๋์งธ, ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ๋ ํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด componentDidMount()
์๋ช
์ฃผ๊ธฐ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ต๋๋ค. ์ฒซ ๋ฒ์งธ API ์์ฒญ ์ ๊ฒ์์ด ์ด๊ธฐ ์ํ ๊ฐ์ ์ฌ์ฉํฉ๋๋ค. ๊ธฐ๋ณธ ๋งค๊ฐ๋ณ์์ธ DEFAULT_QUERY
๊ฐ์ด "redux"์์ผ๋ก, "redux" ๊ธฐ์ฌ๋ฅผ ๋งจ ์ฒ์ ๊ฐ์ ธ์ต๋๋ค.
์
์งธ, fetch API๋ฅผ ์ฌ์ฉํ์ต๋๋ค. ES6 ํ
ํ๋ฆฟ ๋ฆฌํฐ๋ด๋ก searchTerm
๊ณผ ํจ๊ป URL๋ฅผ ๊ตฌ์ฑํ์ต๋๋ค. ๊ตฌ์ฑ๋ URL๋ fetch API์ ์ธ์๋ก ์ ๋ฌ๋ฉ๋๋ค. ์๋ต ๋ฐ์ดํฐ๋ ๋ฐ๋์ JSON ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ก ๋ณํ์์ผ์ผ ํฉ๋๋ค. ๋ง์ง๋ง์ผ๋ก ์ด ๋ฐ์ดํฐ๋ฅผ ์ปดํฌ๋ํธ ๋ด๋ถ ์ํ result
์ ์ ์ฅํฉ๋๋ค. ์์ฒญ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด then()
์ด ์๋ catch()
๊ฐ ์คํ๋ฉ๋๋ค. ๋ค์ ์ฅ์์ ์ค๋ฅ ์ฒ๋ฆฌ๋ฅผ ๋ค๋ฃน๋๋ค.
๋ง์ง๋ง์ผ๋ก ์์ฑ์์ ์ปดํฌ๋ํธ ๋ฉ์๋์ธ setSearchTopStories()
๋ฅผ ๋ฐ์ธ๋ฉ ํ์ต๋๋ค.
์ด์ API๋ก ๊ฐ์ ธ์จ ์ธ๋ถ ๋ฐ์ดํฐ ๋ฆฌ์คํธ๊ฐ ํ์๋ฉ๋๋ค. ์ปดํฌ๋ํธ ๋ด๋ถ ์ํ result
๋ ๋ฉํ ์ ๋ณด์ ์ธ๊ธฐ ๋ฆฌ์คํธ๊ฐ ๊ฐ์ด ๋ด๊ฒจ ๊ฐ์ฒด๊ฐ ๋ณต์กํฉ๋๋ค. render()
๋ฉ์๋ ์์์ console.log(this.state);
๋ก ์ํ๋ฅผ ์ถ๋ ฅํ๊ณ , ๊ฐ๋ฐ์ ๋๊ตฌ๋ฅผ ์ด์ด ๋๋ฒ๊น
ํด๋ณด๋ฉด ๋ฐ์ดํฐ๋ฅผ ํ์ธํด ๋ณผ ์ ์์ต๋๋ค.
๋ค์์ผ๋ก ์กฐ๊ฑด๋ฌธ์ ๋ฐ๋ผ result
์ ๋ ๋๋ง ์ฌ๋ถ๋ฅผ ์ฒ๋ฆฌํด๋ด
์๋ค. ๋จผ์ ์ปดํฌ๋ํธ ๋ด๋ถ ์ํ result
์ ๊ฐ์ด ์๋ ๊ฒฝ์ฐ, null
์ ๋ฐํํ์ฌ ์๋ฌด๊ฒ๋ ๋ ๋๋ง๋์ง ์๊ฒ ํฉ๋๋ค. API ์์ฒญ์ด ์ฑ๊ณตํ๋ฉด ์๋ต ๋ฐ์ดํฐ๊ฐ ์ปดํฌ๋ํธ ๋ด๋ถ ์ํ์ ์ ์ฅ๋๊ณ App ์ปดํฌ๋ํธ๋ ์
๋ฐ์ดํธ๋์ด ๋ค์ ๋ ๋๋ง ๋ฉ๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
...
render() {
# leanpub-start-insert
const { searchTerm, result } = this.state;
if (!result) { return null; }
# leanpub-end-insert
return (
<div className="page">
...
<Table
# leanpub-start-insert
list={result.hits}
# leanpub-end-insert
pattern={searchTerm}
onDismiss={this.onDismiss}
/>
</div>
);
}
}
์ปดํฌ๋ํธ ์๋ช
์ฃผ๊ธฐ ๋์ ์ด๋ค ์ผ์ด ์ผ์ด๋ ๊น์? ์ปดํฌ๋ํธ๋ ์์ฑ์์์ ์ด๊ธฐํ๋ ํ ๋ ๋๋ง ๋ฉ๋๋ค. ๋ด๋ถ ์ํ result
๊ฐ์ด null
์ด๋ฏ๋ก ์๋ฌด๊ฒ๋ ํ์๋์ง ์์ต๋๋ค. ์ดํ componentDidMount()
๋ฉ์๋๊ฐ ์คํ๋ฉ๋๋ค. ์ด ๋ฉ์๋์์ ํด์ปค ๋ด์ค API ์์ฒญ์ ๋ฐ๋ผ ๋น๋๊ธฐ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. ์๋ต ๋ฐ์ดํฐ๊ฐ ๋์ฐฉํ๋ฉด setSearchTopStories()
๋ฉ์๋์์ ๋ด๋ถ ์ปดํฌ๋ํธ ์ํ๋ฅผ ์
๋ฐ์ดํธํฉ๋๋ค. ๊ทธ๋ค์๋ถํฐ ์
๋ฐ์ดํธ ์๋ช
์ฃผ๊ธฐ๊ฐ ์์๋ฉ๋๋ค. render()
๋ฉ์๋๊ฐ ๋ค์ ์คํ๋๋๋ฐ, ์ด๋ฒ์๋ ๋ด๋ถ ์ํ result
๊ฐ ์๊ธฐ ๋๋ฌธ์ ๋ฆฌ์คํธ๊ฐ ํ์๋ฉ๋๋ค. App ์ปดํฌ๋ํธ์ Table ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง ๋ฉ๋๋ค.
๋๋ถ๋ถ์ ๋ธ๋ผ์ฐ์ ์ create-react-app๋ fetch()
๋ฉ์๋๋ฅผ ์ง์ํฉ๋๋ค. fetch()
๋์ ๋
ธ๋ ํจํค์ง์ธ superagent(์ํผ์์ด์ ํธ) ๋๋ axios(์ก์์ค์ค) ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋์
ํ ์ ์์ต๋๋ค.
์ด ์ฑ
์ ์๋ฐ์คํฌ๋ฆฝํธ ๋ถ๋ฆฌ์ธ ์ฐ์ฐ์์ ์ถ์ฝ ํ๊ธฐ๋ฒ์ ์ฌ์ฉํฉ๋๋ค. if (result === null)
๋์ ์ถ์ฝ ํ๊ธฐ๋ฒ์ธ if (!result)
๋ฅผ ์ฌ์ฉํ์ต๋๋ค. ์ดํ์๋ if (list.length === 0)
๋์ if (!list.length)
์, if (someString !== '')
๋์ if (someString)
์ ์ฌ์ฉํ๊ฒ ์ต๋๋ค. ์์ง ์ถ์ฝ ํ๊ธฐ๋ฒ์ ๋ํด ์ ๋ชจ๋ฅด๊ณ ์๋ค๋ฉด, ์ด ๋ถ๋ถ์ ๋ฐ๋์ ํ์ตํ๊ณ ๋์์ค๊ธธ ๋ฐ๋๋๋ค.
๋ค์ ์ ํ๋ฆฌ์ผ์ด์ ์ผ๋ก ๋์๊ฐ์๋ค. ๊ธฐ์ฌ ๋ฆฌ์คํธ๊ฐ ๋ณด์ด๋์ง ํ์ธํด๋ณด์ธ์. ํ์ง๋ง ์์ผ๋ก ๊ณ ์ณ์ผํ ๋ฒ๊ทธ๊ฐ ๋จ์์์ต๋๋ค. ์ฒซ์งธ, "Dismiss" ๋ฒํผ์ ๋๋ค. ๋ฒํผ์ ํด๋ฆญํด๋ ์๋ํ์ง ์์ต๋๋ค. ๊ฐ ๋ฒํผ์ ํด๋น๋๋ ๊ฐ์ฒด๋ฅผ ์๋ณํ์ง ๋ชปํด ๋ฆฌ์คํธ๊ฐ ๋ณ๊ฒฝ๋์ง ์์ต๋๋ค. ๋์งธ, ๊ฒ์ ๊ธฐ๋ฅ์ ๋๋ค. ์ด๊ธฐ ๊ฒ์์ ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค์ง๋ง, ์ดํ์๋ ํด๋ผ์ด์ธํธ์์ ํํฐ๋ง๋ ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. Search ์ปดํฌ๋ํธ์์๋ API ์์ฒญ์ผ๋ก ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ฒ ์์ ํด์ผ ํฉ๋๋ค. ๋ค์ ์ฅ์์ ์ฐจ๊ทผ์ฐจ๊ทผ ํด๊ฒฐํด๋ณด๊ฒ ์ต๋๋ค.
- [MDN] ES6 ํ ํ๋ฆฟ ๋ฆฌํฐ๋ด
- [MDN] the native fetch API
- [์ ์ ๋ธ๋ก๊ทธ] ๋ฆฌ์กํธ์์ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ
"dismiss" ๋ฒํผ์ ํด๋ฆญํ๋ฉด ์๋ฌด๊ฒ๋ ์๋ํ์ง ์์ต๋๋ค. onDismiss()
๋ฉ์๋๋ ํด๋น ๊ฐ์ฒด๋ฅผ ์๋ณํ์ง ๋ชปํ๊ณ ์๊ธฐ ๋๋ฌธ์
๋๋ค. ๋ฆฌ์คํธ ๋ด ๊ฐ ๊ฐ์ฒด๋ฅผ ์๋ณํ ์ ์๊ฒ onDismiss()
๋ฉ์๋๋ฅผ ๊ณ ์ณ๋ด
์๋ค.
{title="src/App.js",lang=javascript}
onDismiss(id) {
const isNotId = item => item.objectID !== id;
# leanpub-start-insert
const updatedHits = this.state.result.hits.filter(isNotId);
this.setState({
...
});
# leanpub-end-insert
}
setState()
๋ฉ์๋ ๋ถ๋ถ์ ์ดํด๋ด
์๋ค. ๋ด๋ถ ์ํ result
๋ ๋ณต์กํ ๊ฐ์ฒด์
๋๋ค. ์ด ์ค ์ธ๊ธฐ ๊ธฐ์ฌ ๋ฆฌ์คํธ๋ result
๊ฐ์ฒด ๋ด hits
ํ๋กํผํฐ์ ํด๋นํฉ๋๋ค. ๋ฒํผ์ ํด๋ฆญํ๋ฉด result
๊ฐ์ฒด์์ ํด๋น ์์ดํ
์ด ์ ๊ฑฐ๋ ๋ฆฌ์คํธ๊ฐ ์
๋ฐ์ดํธ๋์ง๋ง, ๋ค๋ฅธ ํ๋กํผํฐ๋ ๊ทธ๋๋ก ์ ์ง๋ฉ๋๋ค.
๋ฆฌ์กํธ๋ ๋ถ๋ณ ๋ฐ์ดํฐ ๊ตฌ์กฐ ์์น์ ๋ฐ๋ผ ๊ฐ์ฒด ๋๋ ์ํ๋ฅผ ๋ฐ๋ก ๋ณ๊ฒฝํ ์ ์์ต๋๋ค. result
๊ฐ์ฒด ๋ด hits
ํ๋กํผํฐ ๊ฐ์ ์ง์ ์ ์ผ๋ก ๋ณ๊ฒฝํ ์ ์๋๋ผ๋ ์๋์ ๊ฐ์ด ์ฌ์ฉํ๋ฉด ์๋ฉ๋๋ค.
{title="Code Playground",lang="javascript"}
// ์ด๋ ๊ฒ ํ์ง ๋ง์ธ์.
this.state.result.hits = updatedHits;
์ฌ๋ฐ๋ฅธ ์ ๊ทผ๋ฒ์ ์ ๊ฐ์ฒด์ ๋์ผํ ๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ ๊ทธ ์ด๋ค ๊ฐ์ฒด๋ ๋ณ๊ฒฝํ์ง ์๊ณ ๊ทธ๋๋ก ์ ์งํ๋ ๊ฒ์ ๋๋ค.
์ด๋ฅผ ์ํด ES6 Object.assign()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. Object.assign()
์ ์ฒซ ๋ฒ์งธ ์ธ์๋ ํ๊น ๊ฐ์ฒด์ด๊ณ , ๋๋จธ์ง ๋ ์ธ์๋ ์์ค ๊ฐ์ฒด์
๋๋ค. ์ด ํฉ์ณ์ง ๋ ๊ฐ์ฒด๋ฅผ ํ๊น ๊ฐ์ฒด์์ ๋ค์ ํฉ์นฉ๋๋ค. ํ๊น ๊ฐ์ฒด๋ ๋น ๊ฐ์ฒด๊ฐ ๋ ์ ์์ต๋๋ค. ์์ค ๊ฐ์ฒด๋ ๋ณ๊ฒฝ๋์ง ์๊ธฐ ๋๋ฌธ์ ๋ถ๋ณ์ฑ ์์น์ ๊ณ ์ํฉ๋๋ค.
{title="Code Playground",lang="javascript"}
const updatedHits = { hits: updatedHits };
const updatedResult = Object.assign({}, this.state.result, updatedHits);
๋์ผํ ํ๋กํผํฐ ์ด๋ฆ์ ๊ณต์ ํ ๋, ํ์ ๊ฐ์ฒด๋ ์ ์์ ๋ณํฉ ๊ฐ์ฒด๋ฅผ ๋ฎ์ด์๋๋ค. onDismiss()
๋ฉ์๋๋ฅผ ๋ค์ ์์ฑํ๊ฒ ์ต๋๋ค.
{title="src/App.js",lang=javascript}
onDismiss(id) {
const isNotId = item => item.objectID !== id;
const updatedHits = this.state.result.hits.filter(isNotId);
this.setState({
# leanpub-start-insert
result: Object.assign({}, this.state.result, { hits: updatedHits })
# leanpub-end-insert
});
}
๋ฌธ์ ๋ ํด๊ฒฐ๋์์ง๋ง, ์์์ ๋ฐฐ์ด ES6 ์ ๊ฐ ์ฐ์ฐ์(spread operator)๋ฅผ ํ์ฉํด ๋ ๊ฐ๋จํ ์ฝ๋๋ก ๋ง๋ค ์ ์์ต๋๋ค. ...
ํ์๋ ๋ฐฐ์ด ๋๋ ๊ฐ์ฒด์ ๋ชจ๋ ๊ฐ์ ๋ณต์ฌํจ์ ๋ปํฉ๋๋ค.
์๋ ES6 ๋ฐฐ์ด ์คํ๋ ๋ ์ฐ์ฐ์(array spread operator) ์์ ์ฝ๋๋ฅผ ๋ด ์๋ค.
{title="Code Playground",lang="javascript"}
const userList = ['Robin', 'Andrew', 'Dan'];
const additionalUser = 'Jordan';
const allUsers = [ ...userList, additionalUser ];
console.log(allUsers);
// ์ถ๋ ฅ: ['Robin', 'Andrew', 'Dan', 'Jordan']
allUsers
๋ณ์๋ ์ ๋ฐฐ์ด์
๋๋ค. userList
๋ณ์์ additionalUser
๋ณ์ ๊ฐ์ ๊ทธ๋๋ก ์ ์ง๋ฉ๋๋ค. ์ด๋ฐ ๋ฐฉ๋ฒ์ผ๋ก ๋ ๋ฐฐ์ด์ ์ ๋ฐฐ์ด๋ก ํฉ์น ์ ์์ต๋๋ค.
{title="Code Playground",lang="javascript"}
const oldUsers = ['Robin', 'Andrew'];
const newUsers = ['Dan', 'Jordan'];
const allUsers = [ ...oldUsers, ...newUsers ];
console.log(allUsers);
// ์ถ๋ ฅ: ['Robin', 'Andrew', 'Dan', 'Jordan']
์ด์ด์ ๊ฐ์ฒด ์คํ๋ ๋ ์ฐ์ฐ์(object spread operator)๋ฅผ ์์๋ด ์๋ค. ๊ฐ์ฒด ์คํ๋ ๋ ์ฐ์ฐ์๋ ES6 ์ดํ ์ฐจ๊ธฐ ์๋ฐ์คํฌ๋ฆฝํธ๋ฅผ ์ํด ์ ์๋ ๊ธฐ๋ฅ์ด์ง๋ง, ๋ฆฌ์กํธ์ ๋จผ์ ๋์ ๋์์ต๋๋ค. create-react-app ์ญ์ ๊ฐ์ฒด ์คํ๋ ๋ ์ฐ์ฐ์๋ฅผ ์ง์ํฉ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก ES6 ๋ฐฐ์ด ์ ๊ฐ ์ฐ์ฐ์(array spread operator)์ ๊ฐ์ง๋ง, ๋ฐฐ์ด์ด ์๋ ๊ฐ์ฒด๋ผ๋ ์ ์ด ๋ค๋ฆ ๋๋ค. ๊ฐ ํค(key)-๊ฐ(value) ์์ ์ ๊ฐ์ฒด์ ๋ณต์ฌํฉ๋๋ค.
{title="Code Playground",lang="javascript"}
const userNames = { firstname: 'Robin', lastname: 'Wieruch' };
const age = 28;
const user = { ...userNames, age };
console.log(user);
// ์ถ๋ ฅ: { firstname: 'Robin', lastname: 'Wieruch', age: 28 }
๋ฐฐ์ด ์ ๊ฐ์ ๊ฐ์ด ์ฌ๋ฌ ๊ฐ์ฒด๋ฅผ ํผ์น ์ ์์ต๋๋ค.
{title="Code Playground",lang="javascript"}
const userNames = { firstname: 'Robin', lastname: 'Wieruch' };
const userAge = { age: 28 };
const user = { ...userNames, ...userAge };
console.log(user);
// ์ถ๋ ฅ: { firstname: 'Robin', lastname: 'Wieruch', age: 28 }
๋ฐ๋ผ์ Object.assign()
๋ฉ์๋๋ฅผ ๋์ฒดํ ์ ์์ต๋๋ค.
{title="src/App.js",lang=javascript}
onDismiss(id) {
const isNotId = item => item.objectID !== id;
const updatedHits = this.state.result.hits.filter(isNotId);
this.setState({
# leanpub-start-insert
result: { ...this.state.result, hits: updatedHits }
# leanpub-end-insert
});
}
onDismiss()
๋ฉ์๋๊ฐ ์ ์๋ํ๋์ง ๋ค์ ํ์ธํฉ๋๋ค. hits
๋ฆฌ์คํธ ๋ด ๊ฐ ๊ฐ์ฒด๋ฅผ ์๋ณํ์ฌ ํด๋น ์์ดํ
์ ์ ์ธ๋ ๋ฆฌ์คํธ๊ฐ ์
๋ฐ์ดํธ๋๋์ง ํ์ธํฉ๋๋ค.
์กฐ๊ฑด๋ถ ๋ ๋๋ง(conditional rendering)์ ํ๋ ๋๋ ์ฌ๋ฌ ์ปดํฌ๋ํธ์ ์๋ ๋จผํธ์ ๋ ๋๋ง ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํฉ๋๋ค. if-else
๋ฌธ ์ญ์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ์กฐ๊ฑด๋ถ ๋ ๋๋ง ๋ฐฉ๋ฒ์
๋๋ค. 3.2 ์ ์์ ์กฐ๊ฑด๋ถ ๋ ๋๋ง์ ๋ค๋ค์ต๋๋ค.
์ปดํฌ๋ํธ ๋ด๋ถ ์ํ result
์ด๊ธฐ๊ฐ์ null
์
๋๋ค. App ์ปดํฌ๋ํธ์ API ์๋ต ๋ฐ์ดํฐ๊ฐ ๋์ฐฉํ์ง ์์ผ๋ฉด ์๋ฌด๊ฒ๋ ๋ฐํ๋์ง ์์ต๋๋ค. render()
์๋ช
์ฃผ๊ธฐ ๋ฉ์๋ ์คํ ์ ์ ์กฐ๊ฑด์ ๋ฐ๋ผ ๋ฐํ ์ฌ๋ถ๊ฐ ๊ฒฐ์ ๋ฉ๋๋ค. App ์ปดํฌ๋ํธ๋ ์ด๊ธฐ์ ์๋ฌด๊ฒ๋ ๋ ๋๋ง ๋์ง ์๋ค๊ฐ, result
๊ฐ์ด ์
๋ฐ์ดํธ ๋๋ฉด ๋ค์ ๋ ๋๋ง ํ๊ฒ ๋ฉ๋๋ค.
ํ ๋จ๊ณ ๋ ๋์๊ฐ ๋ด
์๋ค. ์กฐ๊ฑด๋ถ ๋ ๋๋ง์ result
์ ์์กดํ๋ Table ์ปดํฌ๋ํธ๋ฅผ ํฌํจํ๊ฒ ์ต๋๋ค. result
๊ฐ์ด ์์ด๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋ ๋ด์ฉ์ด ํ์๋์ด์ผ ํฉ๋๋ค. ์ด๋ฒ์๋ if-else
๋ฌธ์ด ์๋ ์ผํญ ์กฐ๊ฑด ์ฐ์ฐ์(ternary operator)๋ก ์์ฑํด๋ด
์๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
...
render() {
# leanpub-start-insert
const { searchTerm, result } = this.state;
# leanpub-end-insert
return (
<div className="page">
<div className="interactions">
<Search
value={searchTerm}
onChange={this.onSearchChange}
>
Search
</Search>
</div>
# leanpub-start-insert
{ result
? <Table
list={result.hits}
pattern={searchTerm}
onDismiss={this.onDismiss}
/>
: null
}
# leanpub-end-insert
</div>
);
}
}
๋ง์ง๋ง์ผ๋ก ๋
ผ๋ฆฌ ์ฐ์ฐ์ &&
๋ก ์์ฑํฉ๋๋ค. ์๋ฐ์คํฌ๋ฆฝํธ์์ true &&'Hello World'
๊ฒฝ์ฐ 'Hello World'๋ก ํ๊ฐ๋๊ณ , false && 'Hello World'
๊ฒฝ์ฐ ๊ฑฐ์ง์ผ๋ก ํ๊ฐ๋ฉ๋๋ค.
{title="Code Playground",lang="javascript"}
const result = true && 'Hello World';
console.log(result);
// ์ถ๋ ฅ: Hello World
const result = false && 'Hello World';
console.log(result);
// ์ถ๋ ฅ: false
์ ํ๋ฆฌ์ผ์ด์
์๋ ์ ์ฉํด๋ด
์๋ค. ์กฐ๊ฑด์ด ์ฐธ์ด๋ฉด, &&
๋
ผ๋ฆฌ ์ฐ์ฐ์ ๋ท๋ถ๋ถ ๋ด์ฉ์ด ์ถ๋ ฅ๋ฉ๋๋ค. ์กฐ๊ฑด์ด ๊ฑฐ์ง์ด๋ฉด ๋ฆฌ์กํธ๋ ํํ์์ ๋ฌด์ํ๊ณ ์ด๋ฅผ ๊ฑด๋๋๋๋ค. ์ด๋ฒ์๋ ์กฐ๊ฑด์ด ์ฐธ์ด๋ฉด Table ์ปดํฌ๋ํธ๋ฅผ ๋ฐํํ๊ณ ๊ฑฐ์ง์ด๋ฉด ๋ฐํํ์ง ์๊ฒ ๋ง๋ค์ด๋ด
์๋ค.
{title="src/App.js",lang=javascript}
{ result &&
<Table
list={result.hits}
pattern={searchTerm}
onDismiss={this.onDismiss}
/>
}
์ด๋ฒ ์ ์์๋ ๋ฆฌ์กํธ ์กฐ๊ฑด๋ถ ๋ ๋๋ง ์ธ ๊ฐ์ง ๋ฐฉ๋ฒ์ ๋ฐฐ์ ์ต๋๋ค. ์ ์๊ฐ ์ด "์กฐ๊ฑด๋ถ ๋ ๋๋ง" ๊ธ์์ ๋ ๋ง์ ์์ ์ฝ๋๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
์ ํ๋ฆฌ์ผ์ด์
์์ ๊ฐ์ ธ์จ ๋ฐ์ดํฐ๊ฐ ์ ๋ณด์ด๋์ง ํ์ธํ์ธ์. ์์ฒญ ๋ณด๋ฅ ์ค์ผ ๋๋ฅผ ์ ์ธํ๊ณ ๋ชจ๋ ๊ธฐ์ฌ ๋ฆฌ์คํธ๊ฐ ๋ณด์ผ ๊ฒ์
๋๋ค. ์์ฒญ ํ ๋ฐ์ ์๋ต ๋ฐ์ดํฐ๋ฅผ ๋ก์ปฌ ์ํ๋ก ์ ์ฅํ๋ฉด render()
๋ฉ์๋๊ฐ ๋ค์ ์คํ๋๊ณ , ์กฐ๊ฑด๋ถ ๋ ๋๋ง์ ์กฐ๊ฑด์ ๋ฐ๋ผ Table ์ปดํฌ๋ํธ๊ฐ ํ์๋ฉ๋๋ค.
- [๋ฆฌ์กํธ ๊ณต์ ๋ฌธ์] ๋ฆฌ์กํธ ์กฐ๊ฑด ๋ ๋๋ง
- [์ ์ ๋ธ๋ก๊ทธ] ์กฐ๊ฑด๋ฌธ ๋ ๋๋ง์ ๋ค์ํ ๋ฐฉ๋ฒ
Search ์ปดํฌ๋ํธ์ ์
๋ ฅ ํ๋๋ ํด๋ผ์ด์ธํธ์์ ๋ฆฌ์คํธ๋ฅผ ํํฐ๋งํ๊ณ ์์ต๋๋ค. componentDidMount()
๋ฉ์๋์์ ๊ธฐ๋ณธ ๊ฒ์ ์ฉ์ด ๋งค๊ฐ ๋ณ์๋ก ์ป์ ์ฒซ ๋ฒ์งธ API ์๋ต ๋ฐ์ดํฐ ๊ฒฐ๊ด๊ฐ๋ง ๊ฐ์ง๊ณ ์๋ ์ํ์
๋๋ค. ์ด๋ฒ ์ ์์๋ Search ์ปดํฌ๋ํธ๊ฐ ๊ฒ์์ด๋ฅผ ํด๋ผ์ด์ธํธ๊ฐ ์๋ ํด์ปค ๋ด์ค ์๋ฒ์์ ์ ๋ฌํ๋๋ก ๋ฆฌํฉํฐ๋งํ๊ฒ ์ต๋๋ค.
Search ์ปดํฌ๋ํธ์์ ๊ฒ์ ์์ฒญ ์ ํด์ปค๋ด์ค API ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด App ์ปดํฌ๋ํธ์ onSearchSubmit()
๋ฉ์๋๋ฅผ ์๋์ ๊ฐ์ด ์ ์ํ๊ฒ ์ต๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
constructor(props) {
super(props);
this.state = {
result: null,
searchTerm: DEFAULT_QUERY,
};
this.setSearchTopStories = this.setSearchTopStories.bind(this);
this.onSearchChange = this.onSearchChange.bind(this);
# leanpub-start-insert
this.onSearchSubmit = this.onSearchSubmit.bind(this);
# leanpub-end-insert
this.onDismiss = this.onDismiss.bind(this);
}
...
# leanpub-start-insert
onSearchSubmit() {
const { searchTerm } = this.state;
}
# leanpub-end-insert
...
}
onSearchSubmit()
๋ฉ์๋๋ componentDidMount()
๋ฉ์๋๊ณผ ๋์ผํ๊ฒ ๊ฒ์ API๋ฅผ ์์ฒญ ํฉ๋๋ค. ์ฐจ์ด์ ์ ๊ธฐ๋ณธ๊ฐ์ด ์๋ ์์ ๋ ์
๋ ฅ ๊ฒ์์ด์ ์ฌ์ฉํ๋ค๋ ๊ฒ์
๋๋ค. ๋ฐ๋ผ์ ์ด ๊ธฐ๋ฅ์ ๋ผ์ด ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๋ฉ์๋๋ก ๋ง๋ค ์ ์์ต๋๋ค. ์ด๋ฅผ ์ํด ์๋ก fetchSearchTopStories()
๋ฉ์๋๋ฅผ ๋ง๋ค๊ฒ ์ต๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
constructor(props) {
super(props);
this.state = {
result: null,
searchTerm: DEFAULT_QUERY,
};
this.setSearchTopStories = this.setSearchTopStories.bind(this);
# leanpub-start-insert
this.fetchSearchTopStories = this.fetchSearchTopStories.bind(this);
# leanpub-end-insert
this.onSearchChange = this.onSearchChange.bind(this);
this.onSearchSubmit = this.onSearchSubmit.bind(this);
this.onDismiss = this.onDismiss.bind(this);
}
...
# leanpub-start-insert
fetchSearchTopStories(searchTerm) {
fetch(`${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${searchTerm}`)
.then(response => response.json())
.then(result => this.setSearchTopStories(result))
.catch(error => error);
}
# leanpub-end-insert
componentDidMount() {
const { searchTerm } = this.state;
# leanpub-start-insert
this.fetchSearchTopStories(searchTerm);
# leanpub-end-insert
}
...
onSearchSubmit() {
const { searchTerm } = this.state;
# leanpub-start-insert
this.fetchSearchTopStories(searchTerm);
# leanpub-end-insert
}
...
}
๋ค์์ผ๋ก Search ์ปดํฌ๋ํธ์ "Search" ๋ฒํผ์ ์ถ๊ฐํฉ๋๋ค. ์ด ๋ฒํผ์ ํด๋ฆญํด ๊ฒ์ ์์ฒญ์ ๋ณด๋ด๊ณ ํด์ปค ๋ด์ค API์์ ์๋ต ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์๋ด ์๋ค.
๋ฌผ๋ก ์
๋ ฅ ํ๋ ๊ฐ์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค ํด์ปค ๋ด์ค API์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ฒ ํ ์๋ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ onChange()
ํธ๋ค๋ฌ๋ฅผ ์ฌ์ฉํ ์ ์๊ฒ ์ง๋ง ๊ตฌํ์ด ๊ฝค ๋ณต์กํ๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ ์ฌ์ฉํ์ง ์์์ต๋๋ค. ์ด์ ํ๋์ฉ ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค.
์ฒซ์งธ, Search ์ปดํฌ๋ํธ์ onSearchSubmit()
๋ฉ์๋๋ฅผ ์ ๋ฌํฉ๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
...
render() {
const { searchTerm, result } = this.state;
return (
<div className="page">
<div className="interactions">
<Search
value={searchTerm}
onChange={this.onSearchChange}
# leanpub-start-insert
onSubmit={this.onSearchSubmit}
# leanpub-end-insert
>
Search
</Search>
</div>
{ result &&
<Table
list={result.hits}
pattern={searchTerm}
onDismiss={this.onDismiss}
/>
}
</div>
);
}
}
๋์งธ, Search ์ปดํฌ๋ํธ์ ๋ฒํผ์ ์ถ๊ฐํฉ๋๋ค. ๋ฒํผ์ type="submit"
์ ๊ฐ์ง๊ณ , ํผ์ onSubmit()
๋ฉ์๋๋ฅผ ์ ๋ฌํฉ๋๋ค. ๋ฒํผ ๋ด์ฉ์ ์์ ํ๋กํผํฐ children
๋ฅผ ์ฌ์ฉํฉ๋๋ค.
{title="src/App.js",lang=javascript}
# leanpub-start-insert
const Search = ({
value,
onChange,
onSubmit,
children
}) =>
<form onSubmit={onSubmit}>
<input
type="text"
value={value}
onChange={onChange}
/>
<button type="submit">
{children}
</button>
</form>
# leanpub-end-insert
Table ์ปดํฌ๋ํธ์์ ํด๋ผ์ด์ธํธ ๊ฒ์ ๊ธฐ๋ฅ์ ๋ ์ด์ ํ์์์ต๋๋ค. ๋ฐ๋ผ์ isSearched()
ํจ์๋ฅผ ์ญ์ ํฉ๋๋ค. ์ด์ "Search" ๋ฒํผ์ ํด๋ฆญํ๋ฉด ํด์ปค ๋ด์ค API ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ ธ์ฌ ๊ฒ์
๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
...
render() {
const { searchTerm, result } = this.state;
return (
<div className="page">
...
{ result &&
<Table
# leanpub-start-insert
list={result.hits}
onDismiss={this.onDismiss}
# leanpub-end-insert
/>
}
</div>
);
}
}
...
# leanpub-start-insert
const Table = ({ list, onDismiss }) =>
# leanpub-end-insert
<div className="table">
# leanpub-start-insert
{list.map(item =>
# leanpub-end-insert
...
)}
</div>
๊ฒ์ ๋ฒํผ์ ํด๋ฆญํ๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ ์๋ก๊ณ ์นจ ๋๋๋ฐ ์ด๋ HTML ํผ ์ ์ก ์ฝ๋ฐฑ์ ๋ํ ๋ค์ดํฐ๋ธ ๋ธ๋ผ์ฐ์ ๋์์
๋๋ค. ๋ฆฌ์กํธ์์๋ ๋ค์ดํฐ๋ธ ๋ธ๋ผ์ฐ์ ๋์์ ๋ฐฉ์งํ๊ธฐ ์ํด preventDefault()
์ด๋ฒคํธ ๋ฉ์๋๋ฅผ ์์ฃผ ์ฌ์ฉํฉ๋๋ค.
{title="src/App.js",lang=javascript}
# leanpub-start-insert
onSearchSubmit(event) {
# leanpub-end-insert
const { searchTerm } = this.state;
this.fetchSearchTopStories(searchTerm);
# leanpub-start-insert
event.preventDefault();
# leanpub-end-insert
}
์ด๋ฒ ์ ์์๋ ๊ฒ์์ด๋ฅผ ์ ๋ ฅํด ํด์ปค ๋ด์ค ๊ธฐ์ฌ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ธฐ๋ฅ์ ๊ตฌํํ์ต๋๋ค.
- ํด์ปค ๋ด์ค API๋ก ์ด๊ฒ์ ๊ฒ ์คํํด๋ด ๋๋ค.
3.5 ์ ์์ ๋ฐํ๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ์์ธํ ์ดํด๋ณด์๋์? ํด์ปค ๋ด์ค API๋ฅผ ํตํด ๋ ๋ง์ ์ธ๊ธฐ ๊ธฐ์ฌ ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค. ๊ฒฐ๊ด๊ฐ์ page
ํ๋กํผํฐ๋ ํ์ด์ง ๋ฒํธ๋ฅผ ๋ปํฉ๋๋ค. ํ๋กํผํฐ ๊ฐ์ 0
์ผ๋ก ์ฒซ ๋ฒ์งธ ์ฅ์
๋๋ค. ์ด๋ฒ ์ ์์๋ ํ์ด์ง ํ๋กํผํฐ ๊ฐ์ ํตํด ๋ง์ ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ ธ์ค๊ฒ ์ต๋๋ค. ๊ฒ์์ด๋ฅผ ๊ทธ๋๋ก ์ ์ง๋ ์ํ๋ก ๋ค์ ํ์ด์ง ๋ฒํธ๋ฅผ API์ ์ ๋ฌํ๋ฉด ๋ฉ๋๋ค.
๋จผ์ ํ์ด์ง ๋งค๊น ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ ์ ์๊ฒ ๊ธฐ์กด URL ๊ตฌ์ฑ์ ์์ ํฉ์๋ค.
{title="src/App.js",lang=javascript}
const DEFAULT_QUERY = 'redux';
const PATH_BASE = 'https://hn.algolia.com/api/v1';
const PATH_SEARCH = '/search';
const PARAM_SEARCH = 'query=';
# leanpub-start-insert
const PARAM_PAGE = 'page=';
# leanpub-end-insert
๊ทธ๋ฆฌ๊ณ ์ด ์์๋ค์ ๋งค๊ฐ ๋ณ์๋ก ์ถ๊ฐํด URL๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
{title="Code Playground",lang="javascript"}
const url = `${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${searchTerm}&${PARAM_PAGE}`;
console.log(url);
// ์ถ๋ ฅ: https://hn.algolia.com/api/v1/search?query=redux&page=
fetchSearchTopStories()
๋ฉ์๋์์ ํ์ด์ง ๋ฒํธ๋ฅผ ๋ปํ๋ ๋ ๋ฒ์งธ ์ธ์๋ฅผ ์ถ๊ฐํ๊ฒ ์ต๋๋ค. ๋ ๋ฒ์งธ ์ธ์๊ฐ ์์ผ๋ฉด ๊ธฐ๋ณธ๊ฐ์ธ 0
์ ์ ๋ฌํฉ๋๋ค. ๋ฐ๋ผ์ componentDidMount()
๋ฉ์๋์ onSearchSubmit()
๋ฉ์๋๋ ์ต์ด ์์ฒญ ์, ์ฒซ ๋ฒ์งธ ํ์ด์ง๋ฅผ ๊ฐ์ ธ์ต๋๋ค. ์ถ๊ฐ ์์ฒญ ์, ๋ ๋ฒ์งธ ์ธ์๋ฅผ ํตํด ๋ค์ ํ์ด์ง๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
...
# leanpub-start-insert
fetchSearchTopStories(searchTerm, page = 0) {
fetch(`${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${searchTerm}&${PARAM_PAGE}${page}`)
# leanpub-end-insert
.then(response => response.json())
.then(result => this.setSearchTopStories(result))
.catch(error => error);
}
...
}
์ด์ fetchSearchTopStories()
๋ฉ์๋์์ API ์๋ต์ ๋ฐ์ ํ์ฌ ํ์ด์ง๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. "More" ๋ฒํผ์ onClick
ํธ๋ค๋ฌ์์ ์ด ๋ฉ์๋๋ฅผ ์ฌ์ฉํด ๋ ๋ง์ ๋ฆฌ์คํธ์ ๊ฐ์ ธ์ค๊ฒ ๋ง๋ค๊ฒ ์ต๋๋ค. ๋ฒํผ์ ํด๋ฆญํ๋ฉด ๊ทธ๋ค์ ํ์ด์ง ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ฒ ๋ฉ๋๋ค. onClick()
ํธ๋ค๋ฌ์์ ํ์ฌ ๊ฒ์์ด์ ๋ค์ ํ์ด์ง ๋ฒํธ(ํ์ฌ ํ์ด์ง + 1)๋ฅผ ์ ์ํ๊ฒ ์ต๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
...
render() {
const { searchTerm, result } = this.state;
# leanpub-start-insert
const page = (result && result.page) || 0;
# leanpub-end-insert
return (
<div className="page">
<div className="interactions">
...
{ result &&
<Table
list={result.hits}
onDismiss={this.onDismiss}
/>
}
# leanpub-start-insert
<div className="interactions">
<Button onClick={() => this.fetchSearchTopStories(searchTerm, page + 1)}>
More
</Button>
</div>
# leanpub-end-insert
</div>
);
}
}
render()
๋ฉ์๋์์ result
๊ฐ ์์ ๋ ํ์ด์ง ๋ฒํธ ๊ธฐ๋ณธ๊ฐ์ 0
์
๋๋ค. 3.1 ์ ์์ ๋ฐฐ์ ๋ฏ์ด render()
๋ฉ์๋๋ componentDidMount()
์๋ช
์ฃผ๊ธฐ ๋ฉ์๋์์ ๋น๋๊ธฐ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ ์ ์คํ๋ฉ๋๋ค.
์์ง ํ ์ผ์ด ๋ ์์ต๋๋ค. ๋ค์ ํ์ด์ง ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค์ง๋ง, ์ด์ ๋ฐ์ดํฐ๋ฅผ ๋ฎ์ด์ฐ๊ฒ ๋๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ํ๊ธฐ๋ณด๋ค ๊ธฐ์กด ์ํ ๊ฐ์ ์ ๊ฒฐ๊ณผ ๊ฐ์ฒด ๋ฆฌ์คํธ๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ด ๋ฐ๋์งํ ๋ฐฉ๋ฒ์ ๋๋ค.
{title="src/App.js",lang=javascript}
setSearchTopStories(result) {
# leanpub-start-insert
const { hits, page } = result;
const oldHits = page !== 0
? this.state.result.hits
: [];
const updatedHits = [
...oldHits,
...hits
];
this.setState({
result: { hits: updatedHits, page }
});
# leanpub-end-insert
}
setSearchTopStories()
๋ฉ์๋์์ ์ผ์ด๋๋ ์ผ์ ์ค๋ช
ํ๊ฒ ์ต๋๋ค.
์ฒซ์งธ, ๋จผ์ result
๊ฐ์ฒด์์ ํ๋กํผํฐ hits
์ page
๊ฐ์ฒด๋ฅผ ์ป์ต๋๋ค.
๋์งธ, ์ด๋ฏธ hits
๊ฐ ์๋์ง ํ์ธํฉ๋๋ค. page
๋ฒํธ๊ฐ 0
์ผ ๋๋ componentDidMount()
๋ฉ์๋ ๋๋ onSearchSubmit()
๋ฉ์๋์์ ์๋ก์ด ๊ฒ์ ์์ฒญ์ด ์ ๋ฌ๋ฉ๋๋ค. hits
๋ฆฌ์คํธ๊ฐ ๋น์ด์๋ ์ํ์์, "More" ๋ฒํผ์ ํด๋ฆญํ๋ฉด 0
์ด ์๋ ๋ค์ ํ์ด์ง๋ฅผ ์์ฒญํฉ๋๋ค. ์ด์ hits
๋ ์ ์ฅ๋์ด ์ฌ์ฉํ ์ ์์ต๋๋ค.
์
์งธ, ์ด์ hits
์ ์๋ก์ด hits
๋ฅผ ๋ฎ์ด์ฐ๊ธฐ ํ๋ฉด ์ ๋ฉ๋๋ค. ๋ฐ๋ผ์ ๋ ๋ฆฌ์คํธ๋ฅผ ๋ณํฉ ํด์ผ ํฉ๋๋ค. ์ด๋ฅผ ์ํด ES6 ๋ฐฐ์ด ์ ๊ฐ ์ฐ์ฐ์๋ฅผ ์ฌ์ฉํฉ๋๋ค.
๋ท์งธ, hits
์ page
๋ฅผ ์ปดํฌ๋ํธ ๋ด๋ถ ์ํ๋ก ์
๋ฐ์ดํธํฉ๋๋ค.
๋ง์ง๋ง์ผ๋ก "More" ๋ฒํผ ํด๋ฆญํ ๋ ์ผ๋ถ ๋ฆฌ์คํธ๋ง ๊ฐ์ ธ์ค๊ฒ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค. ๋ค์ URL๋ฅผ ์์ ํ๊ฒ ์ต๋๋ค. ์ด์ ๊ณผ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ๊ฐ ๊ฒฝ๋ก๋ฅผ ์์๋ก ๋ง๋ค์ด URL๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
{title="src/App.js",lang=javascript}
const DEFAULT_QUERY = 'redux';
# leanpub-start-insert
const DEFAULT_HPP = '100';
# leanpub-end-insert
const PATH_BASE = 'https://hn.algolia.com/api/v1';
const PATH_SEARCH = '/search';
const PARAM_SEARCH = 'query=';
const PARAM_PAGE = 'page=';
# leanpub-start-insert
const PARAM_HPP = 'hitsPerPage=';
# leanpub-end-insert
์ด ์์๋ฅผ ์กฐํฉํด URL๋ฅผ ๋ง๋ญ๋๋ค.
{title="src/App.js",lang=javascript}
fetchSearchTopStories(searchTerm, page = 0) {
# leanpub-start-insert
fetch(`${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${searchTerm}&${PARAM_PAGE}${page}&${PARAM_HPP}${DEFAULT_HPP}`)
# leanpub-end-insert
.then(response => response.json())
.then(result => this.setSearchTopStories(result))
.catch(error => error);
}
์ด์ ๋ณด๋ค ๋ ๋ง์ ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ ธ์ค๊ฒ ๋์์ต๋๋ค. ์ด๋ฒ ์ ์์๋ ํด์ปค ๋ด์ค API๋ฅผ ํตํด ๋ฐ์ ์ค์ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํด๋ดค์ต๋๋ค. ์ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ตํ ๋, ์ธ๋ถ API๋ฅผ ์ฌ์ฉํ๋ฉด ๋ ์ฌ๋ฏธ์๊ฒ ๋ฐฐ์ธ ์ ์์ ๊ฒ๋๋ค. ์ ๋ ์ด๋ ๊ฒ ๋ฐฐ์ ์ผ๋๊น์.
- ํด์ปค ๋ด์ค API์ ๋งค๊ฐ๋ณ์๋ฅผ ๋ฐ๊ฟ API๋ฅผ ์์ฒญํด๋ด ๋๋ค.
"Search" ๋ฒํผ์ ํด๋ฆญํ๋ฉด ํด์ปค ๋ด์ค API์ ์์ฒญ์ ๋ณด๋ ๋๋ค. ์๋ฅผ ๋ค์ด "redux"๋ฅผ ๊ฒ์ํ๊ณ , ๊ทธ๋ค์ "react"๋ฅผ, ๊ทธ๋ฆฌ๊ณ ๋ค์ "redux"๋ฅผ ์ ๋ ฅํด ๊ฒ์ ์์ฒญ์ ๋ณด๋๋ค๊ณ ๊ฐ์ ํด๋ด ์๋ค. ์ด ์ธ ๋ฒ ๊ฒ์ ์์ฒญ์ ๋ณด๋์ต๋๋ค. ์ด ์ค "redux"๋ฅผ ๋ ๋ฒ ์์ฒญํ๋๋ฐ, ๋ ๋ฒ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด ๋น๋๊ธฐ ์๋ณต ์ฌํ(asynchronous roundtrip)์ ๊ฑฐ์น๊ฒ ๋ฉ๋๋ค. ์ด ๊ฒฝ์ฐ ํด๋ผ์ด์ธํธ ์บ์(client-sided cache)๋ฅผ ๋์ ํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ์บ์(Cache)๋ ๋ฏธ๋ฆฌ ๋ง๋ค์ด์ง ๋ฐ์ดํฐ๋ฅผ ์์๋ก ์ ์ฅํ๋ ๊ณต๊ฐ์ ๋๋ค. ์ฐ๋ฆฌ๊ฐ ๊ตฌํํ ๋ด์ฉ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค. ์ API ์์ฒญ์ ๋ฐ์ผ๋ฉด, ์ด์ ์ ๋์ผํ ์์ฒญ์ด ์๋์ง ๋จผ์ ํ์ธํฉ๋๋ค. ์ด๋ฏธ ์ ์ฅ๋ ์บ์๊ฐ ์์ผ๋ฉด ์ด๋ฅผ ์ฌ์ฉํ๊ณ , ์บ์๊ฐ ์๋ค๋ฉด ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด API๋ฅผ ์์ฒญํฉ๋๋ค. ์ด์ ์์ํด๋ด ์๋ค.
๊ฐ ๊ฒฐ๊ณผ๋ง๋ค ํด๋ผ์ด์ธํธ ์บ์๋ฅผ ๋ณด์ ํ๋ ค๋ฉด, ์ปดํฌ๋ํธ ๋ด๋ถ ์ํ์ result
ํ๋ ๋ณด๋ค, ์ฌ๋ฌ results
๋ฅผ ์ ์ฅํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ์ด๋ฅผ ์ํด results
๊ฐ์ฒด๋ ํค(key)๊ฐ ๊ฒ์์ด์ด๊ณ , ๊ฐ(value)์ด hits
๊ฐ ๋๊ฒ ๋ง๋ค๊ฒ ์ต๋๋ค. API ๊ฒฐ๊ด๊ฐ์ ๊ฒ์์ด(key)์ ๋งคํ๋์ด ์ ์ฅ๋๊ฒ ํด๋ด
์๋ค.
ํ์ฌ ๋ก์ปฌ ์ํ๋ ์๋ ์ฝ๋์ ๋น์ทํ ๊ฒ์ ๋๋ค.
{title="Code Playground",lang="javascript"}
result: {
hits: [ ... ],
page: 2,
}
์๋ฅผ ๋ค์ด, "redux" ๊ทธ๋ฆฌ๊ณ "react"๋ก ๋ ๋ฒ ๊ฒ์ ์์ฒญ์ ๋ณด๋ธ๋ค๋ฉด results
๊ฐ์ฒด๋ ์๋์ ๊ฐ์ ๊ฒ์
๋๋ค.
{title="Code Playground",lang="javascript"}
results: {
redux: {
hits: [ ... ],
page: 2,
},
react: {
hits: [ ... ],
page: 1,
},
...
}
์ด์ setState()
๋ฉ์๋๋ก ํด๋ผ์ด์ธํธ ์บ์๋ฅผ ๊ตฌํํด๋ด
์๋ค.
์ฒซ์งธ, ์ปดํฌ๋ํธ ๋ด๋ถ ์ํ์ result
๊ฐ์ฒด๋ฅผ results
๋ก ๋ณ๊ฒฝํฉ๋๋ค. ๋์งธ, searchKey
๋ฅผ ์ ์ํฉ๋๋ค. searchKey
๋ ์์ ํค(key)๋ก์ result
๋ฅผ ์ ์ฅํ๋๋ฐ ์ฐ์
๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
constructor(props) {
super(props);
this.state = {
# leanpub-start-insert
results: null,
searchKey: '',
# leanpub-end-insert
searchTerm: DEFAULT_QUERY,
};
...
}
...
}
API ์์ฒญ์ ๋ณด๋ด๊ธฐ ์ , searchKey
๋ฅผ ์ค์ ํด์ผ ํฉ๋๋ค. searchKey
์ ๊ฒ์์ด์ธ searchTerm
๋ฅผ ๋ฐ์ํฉ๋๋ค. searchTerm
๊ฐ ์๋ searchKey
๋ฅผ ์ฌ์ฉํ๋ ์ด์ ๋ ๋ฌด์์ผ๊น์? searchTerm
์ Search ์ปดํฌ๋ํธ์์ ์
๋ ฅ ํ๋์ ๊ฒ์์ด๋ฅผ ์
๋ ฅํ ๋๋ง๋ค ๊ทธ ๊ฐ์ด ๋ณ๊ฒฝ๋๋ ๋ณ๊ฒฝ ๋ณ์(fluctuant variable)์
๋๋ค. ๊ฐ์ด ๋ณ๊ฒฝ๋์ง ์๋ ๊ณ ์ ๋ณ์๊ฐ ํ์ํฉ๋๋ค. ๋ฐ๋ผ์ searchKey
๋ก results
๊ฐ์ฒด ๋ด ํด๋นํ๋ ๊ฒฐ๊ณผ๋ฅผ ์ฐพ์ต๋๋ค. ์ด ๋ณ์๋ ์บ์ ๋ด ํ์ฌ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ๋ฆฌํค๋ ํฌ์ธํฐ ์ญํ ์ ํฉ๋๋ค. ๋ฐ๋ผ์ ์ต์ข
์ ์ผ๋ก render()
๋ฉ์๋์ ํ์ฌ ๊ฒฐ๊ณผ๊ฐ ํ์๋ฉ๋๋ค.
{title="src/App.js",lang=javascript}
componentDidMount() {
const { searchTerm } = this.state;
# leanpub-start-insert
this.setState({ searchKey: searchTerm });
# leanpub-end-insert
this.fetchSearchTopStories(searchTerm);
}
onSearchSubmit(event) {
const { searchTerm } = this.state;
# leanpub-start-insert
this.setState({ searchKey: searchTerm });
# leanpub-end-insert
this.fetchSearchTopStories(searchTerm);
event.preventDefault();
}
๋ค์์ผ๋ก searchKey
๋ฅผ ์ฌ์ฉํด ์ปดํฌ๋ํธ ๋ด๋ถ ์ํ์ ๊ฐ result
๋ฅผ ์ ์ฅํ๋๋ก ์์ ํ๊ฒ ์ต๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
...
setSearchTopStories(result) {
const { hits, page } = result;
# leanpub-start-insert
const { searchKey, results } = this.state;
const oldHits = results && results[searchKey]
? results[searchKey].hits
: [];
# leanpub-end-insert
const updatedHits = [
...oldHits,
...hits
];
this.setState({
# leanpub-start-insert
results: {
...results,
[searchKey]: { hits: updatedHits, page }
}
# leanpub-end-insert
});
}
...
}
searchKey
๋ ์
๋ฐ์ดํธ๋ hits
์ page
๋ฅผ results
์ ์ ์ฅํ๊ธฐ ์ํ ํค(key)๋ก ์ฌ์ฉ๋ฉ๋๋ค. ๊ฐ ๋จ๊ณ๋ณ๋ก ์์ธํ ์ค๋ช
ํ๊ฒ ์ต๋๋ค.
์ฒซ์งธ, ์ปดํฌ๋ํธ ๋ด๋ถ ์ํ์์ searchKey
๋ฅผ ๋ฐ์ต๋๋ค. searchKey
๋ componentDidMount()
์ onSearchSubmit()
๋ฉ์๋์์ ์ค์ ํ์ต๋๋ค.
๋์งธ, oldhits
๋ณ์๋ ์ด๋ฏธ ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์๋์ง ํ์ธํ๊ธฐ ์ํด searchKey
์ ํด๋นํ๋ ๋ชฉ๋ก์ ์ฐพ๊ณ ๊ทธ ๊ฐ์ ์ ์ฅํฉ๋๋ค.
์
์งธ, updateHits
๋ณ์๋ ์ด ๊ฒฐ๊ด๊ฐ์ ์ ๋ฌํ๊ธฐ ์ํด ์ด์ ๊ฒฐ๊ณผ์ธ oldhits
์ ์ ๊ฒฐ๊ณผ์ธ hits
๋ฅผ ํฉ์นฉ๋๋ค.
๋ค์์ผ๋ก setState()
์ ์๋ results
๊ฐ์ฒด๋ฅผ ์ดํด๋ด
์๋ค.
{title="src/App.js",lang=javascript}
results: {
...results,
[searchKey]: { hits: updatedHits, page }
}
[searchKey]: { hits: updatedHits, page }
์ด ๋ถ๋ถ์ ์์ธํ ์ค๋ช
ํ๊ฒ ์ต๋๋ค. results
๊ฐ์ฒด์์ searchKey
๋ก ์ฐพ์ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํจ์ ๋งํฉ๋๋ค. ํค(key)๋ searchKey
๋ ๊ฒ์์ด์ด๋ฉฐ, ๊ฐ(value)์ hits
์ page
ํ๋กํผํฐ๊ฐ ์๋ ๊ฐ์ฒด์
๋๋ค. ์ด์ ์ ์์ [searchKey]:...
์ ๊ฐ์ ๊ตฌ๋ฌธ์ ์ด๋ฏธ ๋ฐฐ์ ์ต๋๋ค. ES6์ ๊ณ์ฐ๋ ํ๋กํผํฐ ์ด๋ฆ(ES6 computed property name)๋ก ๊ฐ์ฒด์์ ๊ฐ์ ๋์ ์ผ๋ก ํ ๋นํ ๋ ์ฌ์ฉ๋ฉ๋๋ค.
...results,
๋ถ๋ถ์ ๊ฐ์ฒด ์ ๊ฐ ์ฐ์ฐ์๋ฅผ ์ฌ์ฉํด searchKey
์ ๋ฐ๋ฅธ ๋ชจ๋ results
๋ฅผ ์ ํํฉ๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด ์ด์ ์ ์ ์ฅ๋ ๋ชจ๋ results
๊ฐ ์์ค๋ฉ๋๋ค.
์ด์ ๋ชจ๋ ๊ฒฐ๊ณผ๋ฅผ ๊ฒ์์ด๋ก ์ ์ฅํ๊ฒ ์ต๋๋ค. ๋จผ์ ์บ์๋ฅผ ํ์ฑํํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ๊ณ ์ ๊ฐ์ธ searchKey
์ ๋ฐ๋ผ results
๋ฅผ ๊ฒ์ํฉ๋๋ค. searchKey
๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฉด Search ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ ๋ ๊ทธ ๊ฐ์ด ๊ณ์ ๋ณ๊ฒฝ๋ฉ๋๋ค. ๋ฐ๋ผ์ ๋ณ๋๊ฐ์ธ searchTerm
๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ ๊ฒ์์ด ์ค๋จ๋๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
...
render() {
# leanpub-start-insert
const {
searchTerm,
results,
searchKey
} = this.state;
const page = (
results &&
results[searchKey] &&
results[searchKey].page
) || 0;
const list = (
results &&
results[searchKey] &&
results[searchKey].hits
) || [];
# leanpub-end-insert
return (
<div className="page">
<div className="interactions">
...
</div>
# leanpub-start-insert
<Table
list={list}
onDismiss={this.onDismiss}
/>
# leanpub-end-insert
<div className="interactions">
# leanpub-start-insert
<Button onClick={() => this.fetchSearchTopStories(searchKey, page + 1)}>
# leanpub-end-insert
More
</Button>
</div>
</div>
);
}
}
searchKey
์ ํด๋นํ๋ ๊ฒฐ๊ด๊ฐ์ ์ฐพ์ง ๋ชปํ ๋, list
๋ ๋น์ด์์ต๋๋ค. ์ด๋ ๊ฒ ๋ง๋ค๋ฉด Table ์ปดํฌ๋ํธ๋ฅผ ์กฐ๊ฑด๋ถ ๋ ๋๋ง์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. "More" ๋ฒํผ์ searchTerm
๋ฅผ searchKey
๋ก ์์ ํ์ต๋๋ค. ์ด๋ ๊ฒ ํ์ง ์์ผ๋ฉด searchTerm
์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค ํ์ด์ง ๋งค๊น ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ฒ ๋ฉ๋๋ค. Search ์ปดํฌ๋ํธ ๋ด ์
๋ ฅ ํ๋ ๊ฐ์ด ๋ณ๊ฒฝ๋ ๋ ๋ง๋ค searchTerm
์ ์ ์ฅ๋จ์ ์์ง ๋ง์ธ์.
๊ฒ์ ๊ธฐ๋ฅ์ด ์ ์๋ํ๋์ง, ํด์ปค ๋ด์ค API์์ ๋ฐ์ ๋ฐ์ดํฐ๊ฐ ์ ์ ์ฅ๋๋์ง ํ์ธํ์ธ์.
์ถ๊ฐ๋ก onDismiss()
๋ฉ์๋๋ ๊ฐ์ ํ๊ฒ ์ต๋๋ค. result
๊ฐ์ฒด๊ฐ ์๋, ์ฌ๋ฌ results
๋ฅผ ๋ค๋ฃจ๋๋ก ์์ ํ๊ฒ ์ต๋๋ค.
{title="src/App.js",lang=javascript}
onDismiss(id) {
# leanpub-start-insert
const { searchKey, results } = this.state;
const { hits, page } = results[searchKey];
# leanpub-end-insert
const isNotId = item => item.objectID !== id;
# leanpub-start-insert
const updatedHits = hits.filter(isNotId);
this.setState({
results: {
...results,
[searchKey]: { hits: updatedHits, page }
}
});
# leanpub-end-insert
}
"Dismiss" ๋ฒํผ์ด ์ ๋์ํ๋์ง ํ์ธํ์ธ์.
ํ์ง๋ง ์บ์ ๊ธฐ๋ฅ์ด ์๋ฒฝํ๊ฒ ๊ตฌํ๋์ง ์์์ต๋๋ค. ๊ฒ์ ์์ฒญ ์, API ์์ฒญ์ ๋ง๋ ์ฅ์น๊ฐ ์์ต๋๋ค. ์ด๋ฏธ ๋์ผํ ๊ฒฐ๊ณผ๊ฐ ์์ ๋ API ์์ฒญ์ ๋ฐฉ์งํ๋ ๊ฒ์ฌ๋ฅผ ํ์ง ์์ต๋๋ค. results
๋ ์บ์์ ์ ์ฅ๋์ง๋ง ์ด๋ฅผ ์ฌ์ฉํ์ง ์์์ต๋๋ค. ๋ง์ง๋ง์ผ๋ก ์บ์์์ results
๋ฅผ ์ฌ์ฉํ ์ ์์ ๋, API ์์ฒญ์ ๋ง๋๋ก ์์ ํด๋ด
์๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
constructor(props) {
...
# leanpub-start-insert
this.needsToSearchTopStories = this.needsToSearchTopStories.bind(this);
# leanpub-end-insert
this.setSearchTopStories = this.setSearchTopStories.bind(this);
this.fetchSearchTopStories = this.fetchSearchTopStories.bind(this);
this.onSearchChange = this.onSearchChange.bind(this);
this.onSearchSubmit = this.onSearchSubmit.bind(this);
this.onDismiss = this.onDismiss.bind(this);
}
# leanpub-start-insert
needsToSearchTopStories(searchTerm) {
return !this.state.results[searchTerm];
}
# leanpub-end-insert
...
onSearchSubmit(event) {
const { searchTerm } = this.state;
this.setState({ searchKey: searchTerm });
# leanpub-start-insert
if (this.needsToSearchTopStories(searchTerm)) {
this.fetchSearchTopStories(searchTerm);
}
# leanpub-end-insert
event.preventDefault();
}
...
}
์ด์ ํด๋ผ์ด์ธํธ๋ ๋์ผํ ๊ฒ์์ด๋ฅผ ๋ ๋ฒ ๊ฒ์ํ๋๋ผ๋, ๋จ ํ๋ฒ API ์์ฒญ์ ๋ณด๋
๋๋ค. ํ์ด์ง ๋งค๊น ๋ฐ์ดํฐ๋ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ์บ์๋ฉ๋๋ค. result
์ ํญ์ ๋ง์ง๋ง ํ์ด์ง๊ฐ ์ ์ฅ๋๊ธฐ ๋๋ฌธ์
๋๋ค.
์ด์ ์ ์์ API ๊ฒฐ๊ณผ๋ฅผ ์บ์ฑํ๊ณ ํ์ด์ง ๋งค๊น ๊ธฐ๋ฅ์ ๊ตฌํํด ๋ ๋ง์ ๊ธฐ์ฌ ๋ฆฌ์คํธ๋ฅผ ๊ณ์ ๊ฐ์ ธ์ค๊ฒ ๋ง๋ค์์ต๋๋ค. ๊ทธ๋ฌ๋ ์ ์ผ ์ค์ํ ๋ถ๋ถ์ ํ์ง ์์์ต๋๋ค. ์ํ๊น๊ฒ๋ ๋ง์ ๊ฐ๋ฐ์๋ค์ด ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ฐํ๋ฉด์ ์ค๋ฅ ์ฒ๋ฆฌ(Error Handling)๋ฅผ ๊ฐ๊ณผํ๊ณ ์์ต๋๋ค. ์ค๋ฅ ์ฒ๋ฆฌ๋ฅผ ๋ฌด์ํ ์ฑ ๊ฐ๋ฐํ๋ ๊ฒ์ ์ ๋ง ์ฝ๊ธฐ ๋๋ฌธ์ด์ง์.
์ด๋ฒ ์ ์์๋ API ์์ฒญ ์, ํจ๊ณผ์ ์ธ ์ค๋ฅ ์ฒ๋ฆฌ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณด๊ฒ ์ต๋๋ค. ์ด์ ์ ์์ ๋ก์ปฌ ์ํ์ ์กฐ๊ฑด๋ถ ๋ ๋๋ง์ผ๋ก ์ค๋ฅ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ ์ต๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก ์ค๋ฅ๋ ๋ฆฌ์กํธ์์ ๋ ๋ค๋ฅธ "์ํ(state)"๋ผ๊ณ ๋ณผ ์ ์์ต๋๋ค. ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด ๋ก์ปฌ ์ํ๋ก ์ ์ฅํ๊ณ ์ปดํฌ๋ํธ์ ์กฐ๊ฑด๋ถ ๋ ๋๋ง์ผ๋ก ์ค๋ฅ ๋ฉ์์ง๋ฅผ ํ์ํ ์ ์์ต๋๋ค. ์ฐ๋ฆฌ๋ App ์ปดํฌ๋ํธ์ ์ค๋ฅ ์ฒ๋ฆฌ๋ฅผ ๋ค๋ฃจ๊ฒ ์ต๋๋ค. App ์ปดํฌ๋ํธ๋ ํด์ปค ๋ด์ค API๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฐ๋ ๊ณณ์ด๊ธฐ ์ฃผ์ ์ปดํฌ๋ํธ์ด๊ธฐ ๋๋ฌธ์ ๋๋ค. ๊ทธ๋ผ ์ด์ ์์ํด๋ด ์๋ค.
์ฒซ์งธ, ๋ก์ปฌ ์ํ์ ์ค๋ฅ๋ฅผ ์ถ๊ฐํฉ๋๋ค. error
๊ฐ์ฒด์ ์ด๊ธฐ๊ฐ์ null
์ด๊ณ , ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด ์ค๋ฅ ๋ฉ์์ง ๊ฐ์ผ๋ก ์ค์ ๋ฉ๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
constructor(props) {
super(props);
this.state = {
results: null,
searchKey: '',
searchTerm: DEFAULT_QUERY,
# leanpub-start-insert
error: null,
# leanpub-end-insert
};
...
}
...
}
๋์งธ, fetch API์ catch()
๋ธ๋ก ์์ setState()
๋ฉ์๋๋ก ์ค๋ฅ ๊ฐ์ฒด๋ฅผ ๋ก์ปฌ ์ํ๋ก ์ ์ฅํฉ๋๋ค. API ์์ฒญ์ด ์คํจํ๋ฉด catch ๋ธ๋ก์ด ์คํ๋ฉ๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
...
fetchSearchTopStories(searchTerm, page = 0) {
fetch(`${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${searchTerm}&${PARAM_PAGE}${page}&${PARAM_HPP}${DEFAULT_HPP}`)
.then(response => response.json())
.then(result => this.setSearchTopStories(result))
# leanpub-start-insert
.catch(error => this.setState({ error }));
# leanpub-end-insert
}
...
}
์
์งธ, render()
๋ด ๋ก์ปฌ ์ํ ๋ด error
๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ์ค๊ณ ์กฐ๊ฑด๋ฌธ ๋ ๋๋ง์ผ๋ก ์ค๋ฅ ๋ฉ์์ง๋ฅผ ํ์ํฉ๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
...
render() {
const {
searchTerm,
results,
searchKey,
# leanpub-start-insert
error
# leanpub-end-insert
} = this.state;
...
# leanpub-start-insert
if (error) {
return <p>Something went wrong.</p>;
}
# leanpub-end-insert
return (
<div className="page">
...
</div>
);
}
}
์ฌ๊ธฐ๊น์ง์ ๋๋ค. ์ค๋ฅ ์ฒ๋ฆฌ์ ๋ฌธ์ ์๋์ง๋ฅผ ํ์ธํ๋ ค๋ฉด API URL์ ์กด์ฌํ์ง ์๋ URL๋ก ๋ฐ๊ฟ์ ํ ์คํธํด๋ณด์ธ์.
{title="src/App.js",lang=javascript}
const PATH_BASE = 'https://hn.foo.bar.com/api/v1';
์ ํ๋ฆฌ์ผ์ด์ ๋์ , ์ค๋ฅ ๋ฉ์์ง๊ฐ ํ์๋ฉ๋๋ค. ์ํ๋ ๊ณณ์ ์กฐ๊ฑด๋ถ ๋ ๋๋ง์ผ๋ก ์ค๋ฅ ๋ฉ์์ง๋ฅผ ํ์ํ๋ฉด ๋ฉ๋๋ค. ์กฐ๊ฑด๋ถ ๋ ๋๋ง์ ์ ์ฒด ์ฑ์ผ๋ก ๊ฐ์ธ๊ฒ ๋๋ฉด ๋น ํ๋ฉด๋ง ๋ณด์ผ ๊ฒ์ ๋๋ค. ๋ฐ๋ผ์ Table ์ปดํฌ๋ํธ ๋๋ ์ค๋ฅ ๋ฉ์์ง ๋ ์ค ํ๋๋ฅผ ํ์ํ๋ฉด ์ด๋จ๊น์? ์ค๋ฅ๊ฐ ๋ฐ์ํด๋ ๋๋จธ์ง ์ปดํฌ๋ํธ๋ ํ๋ฉด์ ๋ณด์ด๊ธฐ ๋๋ฌธ์ ์ฌ์ฉ์์๊ฒ ํผ๋์ ์ฃผ์ง ์์ ๊ฒ์ ๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
...
render() {
const {
searchTerm,
results,
searchKey,
error
} = this.state;
const page = (
results &&
results[searchKey] &&
results[searchKey].page
) || 0;
const list = (
results &&
results[searchKey] &&
results[searchKey].hits
) || [];
return (
<div className="page">
<div className="interactions">
...
</div>
# leanpub-start-insert
{ error
? <div className="interactions">
<p>Something went wrong.</p>
</div>
: <Table
list={list}
onDismiss={this.onDismiss}
/>
}
# leanpub-end-insert
...
</div>
);
}
}
์ค๋ฅ ํ ์คํธ๋ฅผ ์ํด ๊ธฐ์กด URL๋ฅผ ์์ ํ๋ค๋ฉด ์ ์ํ๋ก ๋๋๋ ค์ผ ํฉ๋๋ค.
{title="src/App.js",lang=javascript}
const PATH_BASE = 'https://hn.algolia.com/api/v1';
์ ํ๋ฆฌ์ผ์ด์ ์ด ์ ์๋ํ๋์ง ํ์ธํ์ธ์.
{pagebreak}
์ด์ ์ ์์ fetch API๋ก ํด์ปค ๋ด์ค ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์์ต๋๋ค. ํ์ง๋ง ๋ชจ๋ ๋ธ๋ผ์ฐ์ ๊ฐ fetch API๋ฅผ ์ง์ํ๋ ๊ฒ์ ์๋๋๋ค. ํนํ ์ค๋๋ ๋ธ๋ผ์ฐ์ ๋ fetch API๋ฅผ ์ง์ํ์ง ์์ผ๋ฉฐ, ํค๋๋ฆฌ์ค ๋ธ๋ผ์ฐ์ (Headless Browser)์์ ์ ํ๋ฆฌ์ผ์ด์ ์ ํ ์คํธํ ๋, fetch API์ ๊ด๋ จ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ํค๋๋ฆฌ์ค ๋ธ๋ผ์ฐ์ ๋ ์ค์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ๊ตฌ๋๋๋ ๋ธ๋ผ์ฐ์ ๊ฐ ์๋ ํ ์คํธ์ฉ์ผ๋ก ํ ์คํธ ์๋ํ์ ์ฌ์ฉ๋๋ ๋ธ๋ผ์ฐ์ ์ ๋๋ค. ๋ฌผ๋ก ๊ตฌ๋ฒ์ ๋ธ๋ผ์ฐ์ (ํด๋ฆฌํ, polyfill)์ ํ ์คํธ(isomorphic-fetch)์์๋ fetch API๋ฅผ ์ฌ์ฉํ ์ ์๋ ๋ฐฉ๋ฒ์ด ์์ง๋ง, ์ด ์ฑ ์์๋ ์ด ๋ด์ฉ์ ๋ค๋ฃจ์ง ์๊ฒ ์ต๋๋ค.
์ด์ ๊ฐ์ด ๋ธ๋ผ์ฐ์ ๊ฐ ๋ฌธ์ ์ API์ ํ๊ณ๋ฅผ ํด๊ฒฐํ๊ณ ์ ๊ธฐ์กด fetch API๋ฅผ ๋์ฒดํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋์ ํ ์ ์์ต๋๋ค. ๊ทธ ์ค axios๋ ๋ง์ ๋ฆฌ์กํธ ๊ฐ๋ฐ์๋ค์ด ์ฌ์ฉํ๊ณ ์๋ ๋น๋๊ธฐ API ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค. ์ด๋ฒ ์ ์์๋ axios ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น์ ์ฌ์ฉ ๋ฐฉ๋ฒ, ์น ๊ฐ๋ฐ์ ๊ณ ์ง์ ์ธ ๋ฌธ์ ์ ์ธ ๊ตฌ๋ฒ์ ๋ธ๋ผ์ฐ์ ๊ณผ ํค๋๋ฆฌ์ค ๋ธ๋ผ์ฐ์ ํ ์คํธ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณด๊ฒ ์ต๋๋ค.
fetch API๋ฅผ axios๋ก ๋ฐ๊ฟ๋ด ์๋ค.
์ฒซ์งธ, ์ปค๋งจ๋ ๋ผ์ธ์ axios๋ฅผ ์ค์นํฉ๋๋ค.
{title="Command Line",lang="text"}
npm install axios
๋์งธ, App ์ปดํฌ๋ํธ ํ์ผ์ axios๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
{title="src/App.js",lang=javascript}
import React, { Component } from 'react';
# leanpub-start-insert
import axios from 'axios';
# leanpub-end-insert
import './App.css';
...
์ง๊ธ๋ถํฐ fetch()
๋ฉ์๋ ๋์ axios()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ๊ฒ ์ต๋๋ค. ์ฌ์ฉ๋ฒ์ fetch API์ ๊ฑฐ์ ๋์ผํฉ๋๋ค. ์ธ์๋ URL์ด๋ฉฐ ํ๋ก๋ฏธ์ค(promise)๋ฅผ ๋ฐํํฉ๋๋ค. ๋ฐํ๋ ์๋ต์ JSON์ผ๋ก ๋ณํํ์ง ์์๋ ๋ฉ๋๋ค. axios ์ด ์ผ์ ํ๋ฉฐ. ์๋ฐ์คํฌ๋ฆฝํธ์์ data
๊ฐ์ฒด๋ก ๋ฐํํฉ๋๋ค. ๋ฐ๋ผ์ ๋ฐํ๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ ๊ทธ๋๋ก ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
...
fetchSearchTopStories(searchTerm, page = 0) {
# leanpub-start-insert
axios(`${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${searchTerm}&${PARAM_PAGE}${page}&${PARAM_HPP}${DEFAULT_HPP}`)
.then(result => this.setSearchTopStories(result.data))
# leanpub-end-insert
.catch(error => this.setState({ error }));
}
...
}
axios()
๋ฉ์๋๋ ๋ํดํธ๋ก HTTP GET ์์ฒญ์ ๋ณด๋
๋๋ค. HTTP GET ์์ฒญ์ ์ข๋ ๋ช
ํํ๊ฒ ํํํ๋ ค๋ฉด axios.get()
๋ฉ์๋๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๋ง์ฐฌ๊ฐ์ง๋ก HTTP POST ์์ฒญ์ axios.post()
๋ฉ์๋๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ด์ฒ๋ผ axios๋ ๊ฐ๋จํ ์ฝ๋๋ง์ผ๋ก ์๊ฒฉ API ์์ฒญ์ ํ๋ฅญํ๊ฒ ์ํํฉ๋๋ค. ๊ธฐ์กด fetch API๋ ์ฝ๋๊ฐ ๋ณต์กํ๋ฉฐ ํ๋ก๋ฏธ์ค(promise)๋ ์ฒ๋ฆฌํด์ผ ํด์ ๋ถํธํ์ง๋ง, axios๋ก ๊ฐ๋ฐ์ด ํธํ๊ณ ์ฝ๊ฒ ๊ฐ๋ฐํ ์ ์์ต๋๋ค. ํน์ ๋ธ๋ผ์ฐ์ ์ ํค๋๋ฆฌ์ค ๋ธ๋ผ์ฐ์ ํ๊ฒฝ ๋ฌธ์ ์ญ์ ํด๊ฒฐ๋ฉ๋๋ค.
App ์ปดํฌ๋ํธ์์ ํด์ปค ๋ด์ค API ์์ฒญ ์, ๊ณ ์ณ์ผํ ์ ์ด ํ๋ ๋ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด componentDidMount()
์๋ช
์ฃผ๊ธฐ ๋ฉ์๋์์ API ์์ฒญ์ ํ๋ ๋์, ๋ค๋น๊ฒ์ด์
์ ํด๋ฆญํด ๋ค๋ฅธ ํ์ด์ง๋ก ์ด๋ํ๋ค๊ณ ๊ฐ์ ํด๋ด
์๋ค. ๊ทธ๋ฆฌ๊ณ then()
๋๋ catch()
์์ this.setState()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ต๋๋ค. ์ปดํฌ๋ํธ ๋ง์ดํธ๊ฐ ํด์ ๋๋๋ผ๋ ์ฌ์ ํ componentDidMount()
๋ฉ์๋์์๋ ๋ณด๋ฅ ์ค์ธ ์์ฒญ์ด ์กด์ฌํฉ๋๋ค. ์ด ๊ฒฝ์ฐ ๋ธ๋ผ์ฐ์ ๊ฐ๋ฐ์ ๋๊ตฌ๋ ์ปค๋งจ๋ ๋ผ์ธ์์ ์๋์ ๊ฐ์ ๊ฒฝ๊ณ ๋ฉ์์ง๊ฐ ํ์๋ฉ๋๋ค.
{title="Command Line",lang="text"}
Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.
์ด ๊ฒฝ๊ณ ๋ฉ์์ง๋ฅผ ์์ ๊ธฐ ์ํ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ ํด์ ๋ ๋ API ์์ฒญ์ ์ค๋จํ๊ฑฐ๋, ๋ง์ดํธ ํด์ ๋ ์ปดํฌ๋ํธ์์ this.setState()
๋ฉ์๋ ํธ์ถ์ ๋ฐฉ์งํ๋ ๊ฒ์
๋๋ค.
๋ฆฌ์กํธ ๊ฐ๋ฐ์์๋ ๊ฒฝ๊ณ ๋ฉ์์ง ์์ด ๊นจ๋ํ ์ ํ๋ฆฌ์ผ์ด์
์ ์ ์งํ๋ ๊ฒ์ด ๋งค์ฐ ์ค์ํฉ๋๋ค. promise์์ API ์์ฒญ์ ์ค๋จํ๊ฒ ์ฒ๋ฆฌํ๊ฒ ์ต๋๋ค. ์๋ ์ ๊ฐ ์๊ฐํ๋ ๋ฐฉ๋ฒ์ ๋ง์ ๊ฐ๋ฐ์๋ค์ด ๋ฐ๋ฅด๊ณ ์๋ ๋ชจ๋ฒ ์ฌ๋ก์
๋๋ค. ๊ฒฝ๊ณ ๋ฉ์์ง ๋ด์ฉ์ ํด๊ฒฐํ ์ง, ๊ทธ๋๋ก ๋ด๋ฒ๋ ค ๋์ง๋ ํ๋จ์ ๊ฐ์์ ๋ชซ์
๋๋ค. ์ด ์ฑ
์ ํ๋ฐ๋ถ์๋ ๋น์ทํ ๊ฒฝ๊ณ ๋ฉ์์ง๊ฐ ๋์ฌ ๋๋ง๋ค ์๋ ๋ฐฉ๋ฒ์ผ๋ก ํด๊ฒฐํ๋ฉด ๋ฉ๋๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค. ์ปดํฌ๋ํธ ์๋ช
์ฃผ๊ธฐ ์ํ๋ฅผ ๊ฐ๋ฆฌํค๋ ํด๋์ค ํ๋์ธ _isMounted
์ ๋ง๋ญ๋๋ค. ์ด๊ธฐ๊ฐ์ false
์
๋๋ค. ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ ๋ ๋ true
๋ก ๋ณ๊ฒฝ๋๋ฉฐ, ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ ํด์ ๋ ๋ ๋ค์ false
๋ก ๋ณ๊ฒฝ๋ฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ปดํฌ๋ํธ ์๋ช
์ฃผ๊ธฐ๋ฅผ ์ถ์ ํ ์ ์์ต๋๋ค. ๋ก์ปฌ ์ํ ์๋๊ธฐ ๋๋ฌธ์ this.state
์ this.setState()
์ ๋ฌด๊ดํฉ๋๋ค. ํด๋์ค ํ๋ ๊ฐ์ด ๋ณ๊ฒฝ๋์ด๋ ์ปดํฌ๋ํธ๊ฐ ๋ค์ ๋ ๋๋ง ๋์ง ์์ต๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
# leanpub-start-insert
_isMounted = false;
# leanpub-end-insert
constructor(props) {
...
}
...
componentDidMount() {
# leanpub-start-insert
this._isMounted = true;
# leanpub-end-insert
const { searchTerm } = this.state;
this.setState({ searchKey: searchTerm });
this.fetchSearchTopStories(searchTerm);
}
# leanpub-start-insert
componentWillUnmount() {
this._isMounted = false;
}
# leanpub-end-insert
...
}
๋ง์ง๋ง์ผ๋ก ์ฒ์๋ถํฐ ์์ฒญ์ ์ค๋จํ์ง ์๊ณ , ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ ํด์ ๋์๋์ง ํ์ธํ์ฌ this.setState()
๋ฉ์๋ ํธ์ถ์ ํผํ๋๋ก ์์ ํ๊ฒ ์ต๋๋ค. ์ด์ ๊ฒฝ๊ณ ๋ฉ์์ง๊ฐ ์ฌ๋ผ์ง ๊ฒ์
๋๋ค.
{title="src/App.js",lang=javascript}
class App extends Component {
...
fetchSearchTopStories(searchTerm, page = 0) {
axios(`${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${searchTerm}&${PARAM_PAGE}${page}&${PARAM_HPP}${DEFAULT_HPP}`)
# leanpub-start-insert
.then(result => this._isMounted && this.setSearchTopStories(result.data))
.catch(error => this._isMounted && this.setState({ error }));
# leanpub-end-insert
}
...
}
์ด๋ฒ ์ ์์๋ ๋ฆฌ์กํธ์์ ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๋์ฒดํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ ์ต๋๋ค. ์๋ฐ์คํฌ๋ฆฝํธ์๋ ๋ง์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ด ์์ต๋๋ค. ๊ฐ๋ฐ ์ค ๋ฌธ์ ๊ฐ ์๊ธฐ๋ฉด, ์ฌ๋ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ํ๊ณ ์ ํฉํ ์๋ฃจ์
์ ๋์
ํด ํด๊ฒฐํฉ๋๋ค. ๋ํ ๋ง์ดํธ ํด์ ๋ ์ปดํฌ๋ํธ์์ this.setState()
๋ฉ์๋ ํธ์ถ์ ํผํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ ์ต๋๋ค. ๋ฐ๋๋ก axios์์ API ์์ฒญ ์ทจ์๋ฅผ ๋ง๋ ๋ฐฉ๋ฒ๋ ์์ผ๋ ์ง์ ์ฐพ์๋ณด๊ณ ์ค์ตํด๋ณด๊ธธ ๋ฐ๋๋๋ค.
- [์ ์ ๋ธ๋ก๊ทธ] ์ ํ๋ ์์ํฌ๊ฐ ์ค์ํ๊ฐ
- [์ ์ ๋ฆฌํผ์งํ ๋ฆฌ] ๋ฆฌ์กํธ ์ปดํฌ๋ํธ ๋์ฒด ๋ฌธ๋ฒ
{pagebreak}
3์ฅ์์๋ ์ธ๋ถ API๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ ์ต๋๋ค. ์ง๊ธ๊น์ง ํ์ตํ ๋ด์ฉ์ ์ ๋ฆฌํด๋ด ์๋ค.
- ๋ฆฌ์กํธ
- ES6 ํด๋์ค ์ปดํฌ๋ํธ์ ์๋ช ์ฃผ๊ธฐ ๋ฉ์๋์ ์ฌ์ฉ ์ฌ๋ก
componentDidMount()
๋ฉ์๋์์ API ํธ์ถ- ์กฐ๊ฑด๋ถ ๋ ๋๋ง
- ํผ ์ด๋ฒคํธ
- ์๋ฌ ํธ๋ค๋ง
- ES6
- ํ ํ๋ฆฟ ๋ฌธ์์ด ๊ตฌ์ฑ
- ์ ๊ฐ ์ฐ์ฐ์๋ก ๋ถ๋ณ ๋ฐ์ดํฐ ๊ตฌ์กฐ ์์ฑ
- ํ๋กํผํฐ ์ด๋ฆ ๊ณ์ฐ
- ์ผ๋ฐ
- ํด์ปค ๋ด์ค API ์ธํฐ๋ ์
- ๋ค์ดํฐ๋ธ ๋ธ๋ผ์ฐ์ fetch API
- ํด๋ผ์ด์ธํธ ๋ฐ ์๋ฒ ๋ด ๊ฒ์ ๊ธฐ๋ฅ ๊ตฌํ
- ํ์ด์ง๋ค์ด์ ๋ฐ์ดํฐ ํธ์ถ
- ํด๋ผ์ด์ธํธ ๋ ๋๋ง
- fetch ๋์ axios ๋์
์ค์ต ์ฝ๋๋ ๊นํ๋ธ ๋ฆฌํผ์งํ ๋ฆฌ์์ ํ์ธํ ์ ์์ต๋๋ค.