Skip to content
This repository has been archived by the owner on Apr 13, 2020. It is now read-only.

Latest commit

ย 

History

History
1378 lines (1042 loc) ยท 58.2 KB

File metadata and controls

1378 lines (1042 loc) ยท 58.2 KB

3. ์™ธ๋ถ€ API ์‚ฌ์šฉํ•˜๊ธฐ

์ด๋ฒˆ ์žฅ์—์„œ๋Š” ์™ธ๋ถ€ API๋ฅผ ์š”์ฒญํ•˜๊ณ  ์‘๋‹ต๋ฐ›์€ ๋ฐ์ดํ„ฐ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์›๋‹ˆ๋‹ค. API(Application Programming Interface)๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์•ž์„œ, ์ปดํฌ๋„ŒํŠธ ์ƒ๋ช…์ฃผ๊ธฐ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•ด ์•Œ์•„๋ด…๋‹ˆ๋‹ค. ์•„์ง API์— ๋Œ€ํ•ด ์ž˜ ๋ชจ๋ฅด๊ณ  ์žˆ๋‹ค๋ฉด, ์ €์ž๊ฐ€ ์“ด "์•„๋ฌด๋„ ๋‚˜์—๊ฒŒ API๋ฅผ ์•Œ๋ ค์ฃผ์ง€ ์•Š์•˜๋‹ค" ๊ธ€์„ ๋จผ์ € ์ฝ๊ณ  ์˜ค๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค.

ํ•ด์ปค ๋‰ด์Šค(Hacker News)๋Š” ์™€์ด ์ฝค๋น„๋„ค์ดํ„ฐ(Y Combinator, ๋ฏธ๊ตญ ์‹ค๋ฆฌ์ฝ˜๋ฐธ๋ฆฌ๋ฅผ ๋Œ€ํ‘œํ•˜๋Š” ๊ธ€๋กœ๋ฒŒ ์•ก์…€๋Ÿฌ๋ ˆ์ดํ„ฐ)๊ฐ€ ์šด์˜ํ•˜๋Š” ๊ธฐ์ˆ  ๋ถ„์•ผ ๋‰ด์Šค ํ๋ ˆ์ด์…˜ ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค. ํ•ด์ปค ๋‰ด์Šค๋Š” ๋ฐ์ดํ„ฐ ์กฐํšŒ API์™€ ๊ฒ€์ƒ‰ API๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์‹œ์ž‘ํ•˜๊ธฐ ์ „ ํ•ด์ปค ๋‰ด์Šค API ๋ช…์„ธ์„œ๋ฅผ ์ฝ๊ณ  ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ํŒŒ์•…ํ•ด๋‘ก์‹œ๋‹ค.

3.1 ์ƒ๋ช…์ฃผ๊ธฐ ๋ฉ”์„œ๋“œ

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()๋กœ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ๊ฒฌํ•˜์—ฌ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋‚ด๋ถ€ ์ƒํƒœ์— ์ €์žฅํ•˜๊ณ , ์ด ๋‚ด์šฉ์„ ํ™”๋ฉด์— ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฝ์–ด๋ณด๊ธฐ

3.2 ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ

์ด๋ฒˆ ์ ˆ์—์„œ๋Š” ํ•ด์ปค ๋‰ด์Šค 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 ์š”์ฒญ์œผ๋กœ ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ฒŒ ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ์žฅ์—์„œ ์ฐจ๊ทผ์ฐจ๊ทผ ํ•ด๊ฒฐํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ฝ์–ด๋ณด๊ธฐ

3.3 ES6 ์ „๊ฐœ ์—ฐ์‚ฐ์ž

"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 ๋ฆฌ์ŠคํŠธ ๋‚ด ๊ฐ ๊ฐ์ฒด๋ฅผ ์‹๋ณ„ํ•˜์—ฌ ํ•ด๋‹น ์•„์ดํ…œ์„ ์ œ์™ธ๋œ ๋ฆฌ์ŠคํŠธ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

์ฝ์–ด๋ณด๊ธฐ

3.4 ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง

์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง(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 ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

์ฝ์–ด๋ณด๊ธฐ

3.5 Search ์ปดํฌ๋„ŒํŠธ ํด๋ผ์ด์–ธํŠธ ยท ์„œ๋ฒ„ ์ฒ˜๋ฆฌ

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
}

์ด๋ฒˆ ์ ˆ์—์„œ๋Š” ๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅํ•ด ํ•ด์ปค ๋‰ด์Šค ๊ธฐ์‚ฌ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ฝ์–ด๋ณด๊ธฐ

์‹ค์Šตํ•˜๊ธฐ

3.6 ํŽ˜์ด์ง€ ๋งค๊น€ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ

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๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋” ์žฌ๋ฏธ์žˆ๊ฒŒ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์„ ๊ฒ๋‹ˆ๋‹ค. ์ €๋„ ์ด๋ ‡๊ฒŒ ๋ฐฐ์› ์œผ๋‹ˆ๊นŒ์š”.

์‹ค์Šตํ•˜๊ธฐ

3.7 ํด๋ผ์ด์–ธํŠธ ์บ์‹œ

"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์— ํ•ญ์ƒ ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€๊ฐ€ ์ €์žฅ๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

3.8 ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ

์ด์ „ ์ ˆ์—์„œ 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}

3.9 Axios ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ

์ด์ „ ์ ˆ์—์„œ 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.10 ์ •๋ฆฌํ•˜๋ฉด

3์žฅ์—์„œ๋Š” ์™ธ๋ถ€ API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์› ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ๊นŒ์ง€ ํ•™์Šตํ•œ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•ด๋ด…์‹œ๋‹ค.

  • ๋ฆฌ์•กํŠธ
    • ES6 ํด๋ž˜์Šค ์ปดํฌ๋„ŒํŠธ์˜ ์ƒ๋ช…์ฃผ๊ธฐ ๋ฉ”์†Œ๋“œ์™€ ์‚ฌ์šฉ ์‚ฌ๋ก€
    • componentDidMount() ๋ฉ”์„œ๋“œ์—์„œ API ํ˜ธ์ถœ
    • ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง
    • ํผ ์ด๋ฒคํŠธ
    • ์—๋Ÿฌ ํ•ธ๋“ค๋ง
  • ES6
    • ํ…œํ”Œ๋ฆฟ ๋ฌธ์ž์—ด ๊ตฌ์„ฑ
    • ์ „๊ฐœ ์—ฐ์‚ฐ์ž๋กœ ๋ถˆ๋ณ€ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ์ž‘์„ฑ
    • ํ”„๋กœํผํ‹ฐ ์ด๋ฆ„ ๊ณ„์‚ฐ
  • ์ผ๋ฐ˜
    • ํ•ด์ปค ๋‰ด์Šค API ์ธํ„ฐ๋ ‰์…˜
    • ๋„ค์ดํ‹ฐ๋ธŒ ๋ธŒ๋ผ์šฐ์ € fetch API
    • ํด๋ผ์ด์–ธํŠธ ๋ฐ ์„œ๋ฒ„ ๋‚ด ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ ๊ตฌํ˜„
    • ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋ฐ์ดํ„ฐ ํ˜ธ์ถœ
    • ํด๋ผ์ด์–ธํŠธ ๋ Œ๋”๋ง
    • fetch ๋Œ€์‹  axios ๋„์ž…

์‹ค์Šต ์ฝ”๋“œ๋Š” ๊นƒํ—ˆ๋ธŒ ๋ฆฌํผ์ง€ํ† ๋ฆฌ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.