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

Latest commit

ย 

History

History
827 lines (629 loc) ยท 32.9 KB

File metadata and controls

827 lines (629 loc) ยท 32.9 KB

4. ์ปดํฌ๋„ŒํŠธ ๋ชจ๋“ˆ ๊ตฌ์„ฑ ยท ํ…Œ์ŠคํŠธ

์ด๋ฒˆ ์žฅ์€ ๊ทœ๋ชจ๊ฐ€ ํฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ฝ”๋“œ ์œ ์ง€์™€ ๊ด€๋ฆฌ ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด ํด๋”์™€ ํŒŒ์ผ ๊ตฌ์„ฑ ๋ฐฉ๋ฒ•๊ณผ ์กฐ์งํ™”๋œ ์ฝ”๋“œ ์ž‘์„ฑ๋ฒ•์„ ๋ฐฐ์šฐ๊ณ , ๋งˆ์ง€๋ง‰์œผ๋กœ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ๋ฒ•์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ์— ์žˆ์–ด ์ฝ”๋“œ ํ’ˆ์งˆ ํ–ฅ์ƒ์€ ๋งค์šฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฒˆ ์žฅ์—์„œ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์€ ์ž ์‹œ ์ค‘๋‹จํ•ฉ๋‹ˆ๋‹ค.

4.1 ES6 Import ยท Export

ES6์—์„œ๋Š” ๋ชจ๋“ˆ(Modules)์˜ ํŠน์ • ๊ธฐ๋Šฅ์„ ๊ฐ€์ ธ์˜ค๊ณ (import) ๋‚ด๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(export). ์ด ๊ธฐ๋Šฅ์€ ํ•จ์ˆ˜ ํ‘œํ˜„์‹, ํด๋ž˜์Šค ์ปดํฌ๋„ŒํŠธ, ์ƒ์ˆ˜ ๋“ฑ์— ํ•ด๋‹น๋ฉ๋‹ˆ๋‹ค. ๋ชจ๋“ˆ์€ ๋‹จ์ผ ํŒŒ์ผ์ด๊ฑฐ๋‚˜, index ํŒŒ์ผ์ด ์žˆ๋Š” ํด๋” ์ „์ฒด๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

1์žฅ์—์„œ create-react-app์œผ๋กœ ๋ถ€ํŠธ์ŠคํŠธ๋ž˜ํ•‘ ํ•œ ํ›„, ์ƒ์„ฑ๋œ ํŒŒ์ผ์— import๊ณผ export๊ฐ€ ์žˆ๋Š” ๊ฒƒ์„ ๋ณด์•˜์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

import์™€ export์œผ๋กœ ์—ฌ๋Ÿฌ ํŒŒ์ผ์— ๋™์ผํ•œ ์ฝ”๋“œ๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ES6 ์ด์ „์—๋Š” ๋ชจ๋“ˆ ์‚ฌ์šฉ๋ฒ•์— ๊ด€ํ•œ ํ‘œ์ค€ํ™”๊ฐ€ ์—†์—ˆ์œผ๋‚˜, ES6 ์ดํ›„ import์™€ export๋กœ ํ‘œ์ค€ํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ง€๊ธˆ๊นŒ์ง€ ๊ณ„์†ํ•ด์„œ ํ•œ ํŒŒ์ผ์—์„œ ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. import์™€ export์œผ๋กœ ์ฝ”๋“œ๋ฅผ ๋ถ„ํ• ์‹œ์ผœ ์—ฌ๋Ÿฌ ํŒŒ์ผ์„ ๋งŒ๋“ค๋ฉด ์ฝ”๋“œ ์žฌ์‚ฌ์šฉ๊ณผ ์œ ์ง€ ๊ด€๋ฆฌ๊ฐ€ ํŽธํ•ด์ง‘๋‹ˆ๋‹ค.

๊ฐ์ฒด ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ์บก์Šํ™”(encapsulation, ๊ฐ์ฒด์˜ ์†์„ฑ(data fields)๊ณผ ํ–‰์œ„(๋ฉ”์„œ๋“œ, methods)๋ฅผ ํ•˜๋‚˜๋กœ ๋ฌถ๊ณ , ์‹ค์ œ ๊ตฌํ˜„ ๋‚ด์šฉ ์ผ๋ถ€๋ฅผ ์™ธ๋ถ€์— ๊ฐ์ถ”์–ด ์€๋‹‰ํ•ฉ๋‹ˆ๋‹ค.) ๊ฐœ๋…์„ ์ƒ๊ฐํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ํ•œ ํŒŒ์ผ ๋‚ด ๋ชจ๋“  ํ•จ์ˆ˜๋ฅผ ๋ฐ–์œผ๋กœ ๋‚ด๋ณด๋‚ด์•ผ ํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ํ•จ์ˆ˜ ์ค‘ ์ผ๋ถ€๋ฅผ ์ •์˜ํ•ด ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. API์™€ ๊ฐ™์ด ๋ชจ๋“  ํŒŒ์ผ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ฐ–์œผ๋กœ ๋‚ด๋ณด๋‚ด์ง„ ํ•จ์ˆ˜๋งŒ ๋‹ค๋ฅธ ํŒŒ์ผ์—์„œ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ์ œ๋ฅผ ํ†ตํ•ด import์™€ export ์‚ฌ์šฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. file1.js์™€ file2.js์€ ๋ณ€์ˆ˜๋ฅผ ์„œ๋กœ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค. ์ด์™€ ๊ฐ™์ด ์—ฌ๋Ÿฌ ํŒŒ์ผ์—์„œ import์™€ export๋กœ ๋ณ€์ˆ˜๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

export๋Š” ํ•œ ๊ฐœ ์ด์ƒ์˜ ๋ณ€์ˆ˜๋ฅผ ๋‚ด๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

{title="Code Playground: file1.js",lang="javascript"}

const firstname = 'robin';
const lastname = 'wieruch';

export { firstname, lastname };

import์— ์ƒ๋Œ€ ๊ฒฝ๋กœ๋ฅผ ์ง€์ •ํ•˜๊ณ  ๋‚ด๋ณด๋‚ธ ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

{title="Code Playground: file2.js",lang="javascript"}

import { firstname, lastname } from './file1.js';

console.log(firstname);
// ์ถœ๋ ฅ: robin

๋˜ํ•œ ๋‚ด๋ณด๋‚ธ ๋ณ€์ˆ˜๋ฅผ ๋‹ค๋ฅธ ํŒŒ์ผ์—์„œ ๊ฐ์ฒด๋กœ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

{title="Code Playground: file2.js",lang="javascript"}

import * as person from './file1.js';

console.log(person.firstname);
// ์ถœ๋ ฅ: robin

importํ•œ ๋ชจ๋“ˆ์— ๋ณ„๋ช…์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณดํ†ต ์—ฌ๋Ÿฌ ํŒŒ์ผ์— ํ•จ์ˆ˜ ์ด๋ฆ„์ด ์ค‘๋ณต๋  ๊ฒฝ์šฐ, ๋ณ„๋ช…์„ ๋งŒ๋“ค์–ด ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

{title="Code Playground: file2.js",lang="javascript"}

import { firstname as foo } from './file1.js';

console.log(foo);
// ์ถœ๋ ฅ: robin

๋งˆ์ง€๋ง‰์œผ๋กœ default์˜ ์•„๋ž˜์™€ ๊ฐ™์€ ๊ฒฝ์šฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

  • ๋‹จ์ผ ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ๋‚ด๋ณด๋‚ผ ๋•Œ
  • ๋ชจ๋“ˆ API ๋‚ด ์ฃผ์š” ํ•จ์ˆ˜์ž„์„ ๊ฐ•์กฐํ•˜๊ธฐ ์œ„ํ•ด
  • ํด๋ฐฑ import ๊ธฐ๋Šฅ(fallback import functionality)์„ ์œ„ํ•ด

export default์—์„œ {}๋Š” ์ƒ๋žต๋ฉ๋‹ˆ๋‹ค.

{title="Code Playground: file1.js",lang="javascript"}

const robin = {
  firstname: 'robin',
  lastname: 'wieruch',
};

export default robin;

importํ•  ๋•Œ exportํ•œ ์ด๋ฆ„์„ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

{title="Code Playground: file2.js",lang="javascript"}

import developer from './file1.js';

console.log(developer);
// ์ถœ๋ ฅ: { firstname: 'robin', lastname: 'wieruch' }

๋˜ํ•œ export์™€ export default๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

{title="Code Playground: file1.js",lang="javascript"}

const firstname = 'robin';
const lastname = 'wieruch';

const person = {
  firstname,
  lastname,
};

export {
  firstname,
  lastname,
};

export default person;

{title="Code Playground: file2.js",lang="javascript"}

import developer, { firstname, lastname } from './file1.js';

console.log(developer);
// ์ถœ๋ ฅ: { firstname: 'robin', lastname: 'wieruch' }
console.log(firstname, lastname);
// ์ถœ๋ ฅ: robin wieruch

export์—์„œ ๋ฐ”๋กœ ๋ณ€์ˆ˜๋ฅผ ๋‚ด๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

{title="Code Playground: file1.js",lang="javascript"}

export const firstname = 'robin';
export const lastname = 'wieruch';

์ง€๊ธˆ๊นŒ์ง€ ES6 ๋ชจ๋“ˆ์˜ ์ฃผ์š” ๊ธฐ๋Šฅ์„ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ ์ ˆ์—์„œ๋Š” ์งœ์ž„์ƒˆ ์žˆ๋Š” ์ฝ”๋“œ๋กœ ๊ตฌ์„ฑํ•˜์—ฌ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“ˆ API ํ˜•ํƒœ๋กœ ์„ค๊ณ„ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์› ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ์ ˆ์—์„œ ํ•จ์ˆ˜๋ฅผ ๋‚ด๋ณด๋‚ด๊ณ  ๊ฐ€์ ธ์™€์„œ ํ…Œ์ŠคํŠธํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

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

4.2 ES6 ๋ชจ๋“ˆ ๊ตฌ์„ฑ

๊ทธ๋™์•ˆ src/App.js ํŒŒ์ผ์˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ชจ๋“ˆ๋กœ ๋งŒ๋“ค์ง€ ์•Š๊ณ  ๊ฐ™์€ ํŒŒ์ผ์— ์ฝ”๋“œ๋ฅผ ๊ณ„์† ์ž‘์„ฑํ•œ ์ด์œ ๊ฐ€ ๊ถ๊ธˆํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ฒ˜์Œ ๋ฆฌ์•กํŠธ๋ฅผ ์‹œ์ž‘ํ•  ๋•Œ๋Š” ํ•œ ํŒŒ์ผ์—์„œ ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋ฆฌ์•กํŠธ ๊ฐœ๋ฐœ์ด ์ต์ˆ™ํ•ด์ง€๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ทœ๋ชจ๊ฐ€ ์ปค์ง€๋ฉด ๊ทธ๋•Œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ชจ๋“ˆ๋กœ ๋ถ„ํ• ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ง€๊ธˆ๊นŒ์ง€ ๋งŒ๋“  ํ•ด์ปค ๋‰ด์Šค ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์•„๋ž˜์™€ ๊ฐ™์€ ๋ชจ๋“ˆ๋กœ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

{title="Folder Structure",lang="text"}

src/
  index.js
  index.css
  App.js
  App.test.js
  App.css
  Button.js
  Button.test.js
  Button.css
  Table.js
  Table.test.js
  Table.css
  Search.js
  Search.test.js
  Search.css

์ด ๊ตฌ์„ฑ์—์„œ ํŒŒ์ผ๋ช…์€ ๊ฐ™์ง€๋งŒ ํ™•์žฅ์ž๋Š” ๋‹ค๋ฅด๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ™์€ ํŒŒ์ผ๋ช…์œผ๋กœ ๋ฌถ์œผ๋ฉด ์ข€๋” ์ฒด๊ณ„์ ์ธ ๋ชจ๋“ˆ ๊ตฌ์กฐ๋กœ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

{title="Folder Structure",lang="text"}

src/
  index.js
  index.css
  App/
    index.js
    test.js
    index.css
  Button/
    index.js
    test.js
    index.css
  Table/
    index.js
    test.js
    index.css
  Search/
    index.js
    test.js
    index.css

ํด๋” ๊ตฌ์กฐ๊ฐ€ ๊นจ๋—ํ•ด์กŒ์ง€ ์•Š๋‚˜์š”? index.js์€ ํด๋”์˜ ์ฒซ ์ง„์ž…์ ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. index.js๋Š” ๋„ค์ด๋ฐ ์ปจ๋ฒค์…˜์œผ๋กœ ๋‹ค๋ฅธ ํŒŒ์ผ๋ช…์œผ๋กœ ๋ฐ”๊ฟ”๋„ ๋ฌด๋ฐฉํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ๋ชจ๋“ˆ์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ •์˜๋œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ, ์Šคํƒ€์ผ ํŒŒ์ผ, ํ…Œ์ŠคํŠธ ํŒŒ์ผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ์œผ๋กœ App ์ปดํฌ๋„ŒํŠธ์— ์žˆ๋Š” ์ƒ์ˆ˜๋ฅผ ๊บผ๋‚ด ์ƒˆ ํŒŒ์ผ์„ ๋งŒ๋“ค๊ฒ ์Šต๋‹ˆ๋‹ค. ํ•ด์ปค ๋‰ด์Šค API ์š”์ฒญ URL๋ฅผ ๊ตฌ์„ฑํ•  ๋•Œ ์ •์˜ํ•œ ์ƒ์ˆ˜๋ฅผ ๋งํ•ฉ๋‹ˆ๋‹ค.

{title="Folder Structure",lang="text"}

src/
  index.js
  index.css
# leanpub-start-insert
  constants/
    index.js
  components/
# leanpub-end-insert
    App/
      index.js
      test.js
      index..css
    Button/
      index.js
      test.js
      index..css
    ...

src/constants/ ์™€ src/components/ ํด๋”๋กœ ๊ตฌ๋ถ„ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ src/constants/index.js ํŒŒ์ผ์€ ์•„๋ž˜์™€ ๊ฐ™์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

{title="Code Playground: src/constants/index.js",lang="javascript"}

export const DEFAULT_QUERY = 'redux';
export const DEFAULT_HPP = '100';
export const PATH_BASE = 'https://hn.algolia.com/api/v1';
export const PATH_SEARCH = '/search';
export const PARAM_SEARCH = 'query=';
export const PARAM_PAGE = 'page=';
export const PARAM_HPP = 'hitsPerPage=';

์ด์–ด์„œ App/index.js ํŒŒ์ผ์—์„œ ์‚ฌ์šฉํ•  ์ƒ์ˆ˜๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

{title="Code Playground: src/components/App/index.js",lang=javascript}

import {
  DEFAULT_QUERY,
  DEFAULT_HPP,
  PATH_BASE,
  PATH_SEARCH,
  PARAM_SEARCH,
  PARAM_PAGE,
  PARAM_HPP,
} from '../constants/index.js';

...

index.js ๋„ค์ด๋ฐ ์ปจ๋ฒค์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ์ƒ๋Œ€ ๊ฒฝ๋กœ์—์„œ ํŒŒ์ผ ์ด๋ฆ„์„ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

{title="Code Playground: src/components/App/index.js",lang=javascript}

import {
  DEFAULT_QUERY,
  DEFAULT_HPP,
  PATH_BASE,
  PATH_SEARCH,
  PARAM_SEARCH,
  PARAM_PAGE,
  PARAM_HPP,
# leanpub-start-insert
} from '../constants';
# leanpub-end-insert

...

index.js ํŒŒ์ผ์— ์–ด๋–ค ๋น„๋ฐ€์ด ์žˆ๋Š” ๊ฑธ๊นŒ์š”? ์ด ๋„ค์ด๋ฐ ์ปจ๋ฒค์…˜์€ node.js์—์„œ ๋„์ž…๋˜์—ˆ์Šต๋‹ˆ๋‹ค. index ํŒŒ์ผ์€ ๋ชจ๋“ˆ์˜ ์ง„์ž…์ ์œผ๋กœ ์™ธ๋ถ€ ๋ชจ๋“ˆ์—์„œindex.js ํŒŒ์ผ์„ ์‚ฌ์šฉํ•ด ๋ชจ๋“ˆ์—์„œ ํผ๋ธ”๋ฆญ(public) ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์•„๋ž˜์™€ ๊ฐ™์€ ํด๋” ๊ตฌ์กฐ๊ฐ€ ์žˆ๋‹ค๊ณ  ํ•ฉ์‹œ๋‹ค.

{title="Folder Structure",lang="text"}

src/
  index.js
  App/
    index.js
  Buttons/
    index.js
    SubmitButton.js
    SaveButton.js
    CancelButton.js

Buttons/ ํด๋”์—๋Š” ์—ฌ๋Ÿฌ ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ํŒŒ์ผ์—๋Š” export default ๋ฌธ์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‚ด๋ณด๋‚ด๊ณ  Buttons/index.js์—์„œ ๋ชจ๋“  ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ๋‹ค์‹œ Buttons/index.js ํŒŒ์ผ์—์„œ ๊ฐ€์ ธ์˜จ ๋ชจ๋“  ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ๋‚ด๋ณด๋ƒ…๋‹ˆ๋‹ค. index.js ํŒŒ์ผ์€ ๊ณต์šฉ ๋ชจ๋“ˆ API(public module API) ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

{title="Code Playground: src/Buttons/index.js",lang="javascript"}

import SubmitButton from './SubmitButton';
import SaveButton from './SaveButton';
import CancelButton from './CancelButton';

export {
  SubmitButton,
  SaveButton,
  CancelButton,
};

src/App/index.js๋Š” index.js์˜ ํผ๋ธ”๋ฆญ ๋ชจ๋“ˆ API๋ฅผ ํ†ตํ•ด์„œ๋งŒ ๋ฒ„ํŠผ์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

{title="Code Playground: src/App/index.js",lang=javascript}

import {
  SubmitButton,
  SaveButton,
  CancelButton
} from '../Buttons';

์ด์ œ๋ถ€ํ„ฐ index.js๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ํŒŒ์ผ๋กœ ์ ‘๊ทผํ•ด ๋ชจ๋“ˆ์„ ๊ฐ€์ ธ์˜ค๋ฉด ์•ˆ๋ฉ๋‹ˆ๋‹ค. ์บก์Šํ™” ๊ทœ์น™์„ ๊นจํŠธ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

{title="Code Playground: src/App/index.js",lang=javascript}

// ์ด๋ ‡๊ฒŒ ํ•˜์ง€ ๋งˆ์„ธ์š”.
import SubmitButton from '../Buttons/SubmitButton';

์ด๋ฒˆ ์ ˆ์—์„œ ์บก์Šํ™” ๊ทœ์น™์— ๋”ฐ๋ผ ์ฝ”๋“œ๋ฅผ ๋ฆฌํŒฉํ„ฐ๋ง ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์ด ์ฑ…์„ ๋ชจ๋‘ ๋งˆ์นœ ํ›„, ๊ฐœ์ธ ์ˆ™์ œ๋กœ ํ•ด๋ณด๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค.

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

  • ์ฑ…์„ ๋‹ค ๋งˆ์นœ ํ›„ src/App.js ์„ ๋ฆฌํŒฉํ† ๋งํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ ๋ชจ๋“ˆ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

4.3 Jest ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ

์ด ์ฑ…์—์„œ๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ๊นŠ๊ฒŒ ๋‹ค๋ฃจ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ ํ…Œ์ŠคํŠธ๋Š” ํ•„์ˆ˜์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ผญ ์•Œ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ์•ผ ๋ง๋กœ ์ฝ”๋“œ์˜ ํ’ˆ์งˆ์„ ๋†’์ด๊ณ  ๋ชจ๋“  ๊ฒƒ์ด ๋ฌธ์ œ์—†์ด ์ž˜ ์ž‘๋™ํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ž„์„ ๋ณด์žฅํ•˜๊ธฐ ๋•Œ๋ฌธ์ด์ง€์š”.

ํ…Œ์ŠคํŒ… ํ”ผ๋ผ๋ฏธ๋“œ(testing pyramid)์— ๋Œ€ํ•ด ํ•œ ๋ฒˆ์ฏค ๋“ค์–ด๋ดค์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ…Œ์ŠคํŒ… ํ”ผ๋ผ๋ฏธ๋“œ๋Š” ์ œ์ผ ์œ„ ๊ผญ๋Œ€๊ธฐ์—์„œ๋ถ€ํ„ฐ ์ข…๋‹จ ๊ฐ„ ํ…Œ์ŠคํŠธ(end-to-end test), ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ(integration test), ๋‹จ์œ„ ํ…Œ์ŠคํŠธ(unit test)๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ์— ์ต์ˆ™ํ•˜์ง€ ๋ชปํ•œ ๋ถ„๋“ค์„ ์œ„ํ•ด ๊ฐ„๋žตํ•œ ๊ฐœ๋…๋งŒ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ๋…๋ฆฝ์ ์ด๊ณ  ์ž‘์€ ์ฝ”๋“œ ๋ธ”๋ก์„ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค. ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๋Š” ๋‹จ์ผ ํ•จ์ˆ˜๋ฅผ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ํ•œ ๋‹จ์œ„(unit)๊ฐ€ ๋…๋ฆฝ์ ์ผ ๊ฒฝ์šฐ ๋ฌธ์ œ๊ฐ€ ์—†์ง€๋งŒ, ์—ฌ๋Ÿฌ ๋‹จ์œ„๊ฐ€ ๊ฒฐํ•ฉํ•˜๋Š” ๊ฒฝ์šฐ ์ž‘๋™์— ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ, ๋‹จ์œ„๋“ค์„ ๊ทธ๋ฃน์œผ๋กœ ๋ฌถ์–ด ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์‹ค์‹œํ•ด ๊ฐ ๋‹จ์œ„ ๊ทธ๋ฃน์ด ์ž˜ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ์ข…๋‹จ ๊ฐ„ ํ…Œ์ŠคํŠธ๋ฅผ ์‹ค์‹œํ•ด ์‹ค์ œ ์‚ฌ์šฉ์ž ์‹œ๋‚˜๋ฆฌ์˜ค ์‹œ๋ฌผ๋ ˆ์ด์…˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์— ์›น ์ž๋™ํ™” ํ…Œ์ŠคํŠธ(Web Automation Testing)๋ฅผ ๋„์ž…ํ•˜๋Š” ๊ฒƒ์ด ์ข…๋‹จ ๊ฐ„ ํ…Œ์ŠคํŠธ์˜ ๋Œ€ํ‘œ์ ์ธ ์˜ˆ์ž…๋‹ˆ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด ๊ฐ ์œ ํ˜•๋ณ„๋กœ ๋ช‡ ๊ฐœ์˜ ํ…Œ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ• ๊นŒ์š”? ์ผ๋ฐ˜์ ์œผ๋กœ ๋…๋ฆฝ์ ์ธ ํ•จ์ˆ˜์ผ ๊ฒฝ์šฐ ๋งŽ์€ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ์— ๋น„ํ•ด ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ์™€ ์ข…๋‹จ ๊ฐ„ ํ…Œ์ŠคํŠธ์˜ ์ˆ˜๋Š” ์ ์Šต๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์ธ ํ…Œ์ŠคํŠธ ๊ณผ์ •์€ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์‹ค์‹œ ํ›„ ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ์ •์ƒ์ ์ธ ์ž‘๋™ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ๋ช‡ ๊ฐ€์ง€ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์‹ค์‹œํ•˜๊ณ , ๋งˆ์ง€๋ง‰์œผ๋กœ ์ตœ์ข… ์‚ฌ์šฉ์ž ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์ ๊ฒ€ํ•˜๊ธฐ ์œ„ํ•ด ๋ช‡ ๊ฐ€์ง€ ์ข…๋‹จ ๊ฐ„ ํ…Œ์ŠคํŠธ๋ฅผ ์‹ค์‹œํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด ๋ฆฌ์•กํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์–ด๋–ป๊ฒŒ ๋„์ž…ํ•ด์•ผ ํ• ๊นŒ์š”? ๊ธฐ๋ณธ์ ์ธ ๋ฆฌ์•กํŠธ ํ…Œ์ŠคํŠธ๋Š” ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ๋ฅผ ๋งํ•˜๋Š”๋ฐ, ์ด๋Š” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์™€ ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ์— ํ•ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฒˆ ์ ˆ์—์„œ๋Š” Jest(์ œ์ŠคํŠธ) ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ(snapshot test) ์ฝ”๋“œ ์ž‘์„ฑ๋ฒ•์„ ๋ฐฐ์›๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ ์ ˆ์—์„œ ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ, Enzyme(์—”์ž์ž„) ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ๋ฒ•์„ ๋ฐฐ์›๋‹ˆ๋‹ค.

Jest๋Š” ํŽ˜์ด์Šค๋ถ์—์„œ ๋งŒ๋“  ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ๋กœ์จ ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. create-react-app์— Jest๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์–ด ์ƒˆ๋กœ ์„ค์น˜ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

์ฒซ ๋ฒˆ์งธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ทธ์ „์— src/App.jsํŒŒ์ผ์—์„œ ํ…Œ์ŠคํŠธํ•  ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‚ด๋ณด๋‚ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ํ•ด์•ผ ๋‹ค๋ฅธ ํŒŒ์ผ์—์„œ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. 4.1 ์ ˆ์—์„œ ์ด๋ฏธ ๋ฐฐ์šด ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค. export๋กœ Button, Search, Table ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‚ด๋ณด๋ƒ…๋‹ˆ๋‹ค.

{title="src/App.js",lang=javascript}

...

class App extends Component {
  ...
}

...

export default App;

# leanpub-start-insert
export {
  Button,
  Search,
  Table,
};
# leanpub-end-insert

create-react-app์—์„œ ์ œ๊ณตํ•˜๋Š” App.test.js ํŒŒ์ผ์ด ๋ฐ”๋กœ ์ฒซ ๋ฒˆ์งธ ํ…Œ์ŠคํŠธ ํŒŒ์ผ์— ํ•ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ์ด ํ…Œ์ŠคํŠธ๋Š” App ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์˜ค๋ฅ˜ ์—†์ด ๋ Œ๋”๋ง ๋˜๊ณ  ์žˆ๋Š”์ง€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

{title="src/App.test.js",lang=javascript}

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

it('renders without crashing', () => {
  const div = document.createElement('div');
  ReactDOM.render(<App />, div);
  ReactDOM.unmountComponentAtNode(div);
});

"it" ๋ฌธ์€ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ๋งํ•ฉ๋‹ˆ๋‹ค. ๊ตฌ๋ฌธ ์•ˆ์— ํ…Œ์ŠคํŠธ ๋‚ด์šฉ์ด ์žˆ๊ณ , ํ…Œ์ŠคํŠธ ์‹œ ์„ฑ๊ณต ๋˜๋Š” ์‹คํŒจ ์—ฌ๋ถ€๋ฅผ ์•Œ๋ ค์ค๋‹ˆ๋‹ค. "describe" ๋ฌธ์œผ๋กœ "it" ๋ฌธ ์ „์ฒด๋ฅผ ํ…Œ์ŠคํŠธ ์ŠˆํŠธ(test suit)๋กœ ๊ฐ์Œ€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ์ŠˆํŠธ๋Š” ํŠน์ • ์ปดํฌ๋„ŒํŠธ์˜ "it" ๋ฌธ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ๋‚˜์ค‘์— "describe" ๋ฌธ์— ๋Œ€ํ•ด ์ž์„ธํžˆ ํ•™์Šตํ•˜๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค. ์ด๋“ค ๋ชจ๋‘ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ๋ถ„๋ฆฌํ•˜๊ณ  ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

์ปค๋งจ๋“œ ๋ผ์ธ์—์„œ create-react-app ํ…Œ์ŠคํŠธ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฌ์šฉํ•ด ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์— ๋Œ€ํ•œ ์ถœ๋ ฅ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ด๋ด…์‹œ๋‹ค.

{title="Command Line",lang="text"}

npm test

์ค‘์š” App ์ปดํฌ๋„ŒํŠธ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด, componentDidMount() ์ƒ๋ช…์ฃผ๊ธฐ ๋ฉ”์„œ๋“œ ์•ˆ์— fetchSearchTopStories() ๋ฉ”์„œ๋“œ์—์„œ fetch ๋ฉ”์„œ๋“œ๊ฐ€ ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ์•„๋ž˜ ์ง€์‹œ์— ๋”ฐ๋ผ ํ•ด๊ฒฐํ•ฉ์‹œ๋‹ค.

  • ์ปค๋งจ๋“œ ๋ผ์ธ์—์„œ npm install isomorphic-fetch ๋ช…๋ น์–ด๋กœ isomorphic-fetch ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.
  • App.js ํŒŒ์ผ์—์„œ isomorphic-fetch ๋ชจ๋“ˆ์„ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด import fetch from 'isomorphic-fetch'; ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

์ด์ œ Jest ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด ํ…Œ์ŠคํŠธ๋Š” ๋ Œ๋”๋ง ๋œ ์ปดํฌ๋„ŒํŠธ ์Šค๋ƒ…์ƒท ๋งŒ๋“ค๊ณ  ์ฐจ๊ธฐ ์Šค๋ƒ…์ƒท(future snapshot)๊ณผ ๋น„๊ตํ•˜๋Š” ์Šค๋ƒ…์ƒท์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ์—์„œ ์ฐจ๊ธฐ ์Šค๋ƒ…์ƒท์ด ๋ณ€๊ฒฝ ์—ฌ๋ถ€๋ฅผ ์•Œ๋ ค์ค๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๋ฅผ ์˜๋„์ ์œผ๋กœ ์ˆ˜์ •ํ–ˆ์„ ๊ฒฝ์šฐ, ๋˜๋Š” ์ˆ˜์ •์„ ์›์น˜ ์•Š์€ ๊ฒฝ์šฐ์— ์Šค๋ƒ…์ƒท ๋ณ€๊ฒฝ์„ ์ˆ˜๋ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ Œ๋”๋ง ๊ฒฐ๊ณผ์™€ ๋ณ€๊ฒฝ ์‚ฌํ•ญ(diff) ๋งŒ์„ ๊ฐ€์ง€๊ณ  ํ…Œ์ŠคํŠธํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž˜ ๋ณด์™„ํ•ด์ค๋‹ˆ๋‹ค. ์˜๋„์— ๋งž๊ฒŒ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค๋ฉด ๋ณ€๊ฒฝ๋œ ์Šค๋ƒ…์ƒท๋งŒ ๊ฐ„๋‹จํžˆ ์ˆ˜๋ฝํ•˜๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๊ด€๋ฆฌ๊ฐ€ ์–ด๋ ต์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Jest๋Š” ์Šค๋ƒ…์ƒท์„ ํด๋”์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๋ฐฉ๋ฒ•์œผ๋กœ ์ฐจ๊ธฐ ์Šค๋ƒก์ƒท๊ณผ ์ฐจ์ด๋ฅผ ๋น„๊ตํ•˜์—ฌ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ๋‹จ์ผ ํด๋”์— ์Šค๋ƒ…์ƒท์„ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์ „, react-test-renderer ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

{title="Command Line",lang="text"}

npm install --save-dev react-test-renderer

์ด์ œ ์ฒซ ๋ฒˆ์งธ ์Šค๋ƒก์ƒท ํ…Œ์ŠคํŠธ๋กœ App ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ™•์žฅํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋จผ์ € ๋…ธ๋“œ ํŒจํ‚ค์ง€์—์„œ ์„ค์น˜ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , App ์ปดํฌ๋„ŒํŠธ๋ฅผ "it" ๋ฌธ์œผ๋กœ ๊ฐ์‹ผ ๋‹ค์Œ, ์ „์ฒด๋ฅผ "describe" ๋ฌธ์œผ๋กœ ๊ฐ์Œ‰๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ํ…Œ์ŠคํŠธ ์ŠˆํŠธ๋Š” App ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ€๋ฆฌํ‚ต๋‹ˆ๋‹ค.

{title="src/App.test.js",lang=javascript}

import React from 'react';
import ReactDOM from 'react-dom';
# leanpub-start-insert
import renderer from 'react-test-renderer';
# leanpub-end-insert
import App from './App';

# leanpub-start-insert
describe('App', () => {
# leanpub-end-insert

  it('renders without crashing', () => {
    const div = document.createElement('div');
    ReactDOM.render(<App />, div);
  });

# leanpub-start-insert
});
# leanpub-end-insert

์ด์ œ "test" ๋ฌธ์œผ๋กœ ์ฒซ ๋ฒˆ์งธ ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

{title="src/App.test.js",lang=javascript}

import React from 'react';
import ReactDOM from 'react-dom';
import renderer from 'react-test-renderer';
import App from './App';

describe('App', () => {

  it('renders without crashing', () => {
    const div = document.createElement('div');
    ReactDOM.render(<App />, div);
    ReactDOM.unmountComponentAtNode(div);
  });

# leanpub-start-insert
  test('has a valid snapshot', () => {
    const component = renderer.create(
      <App />
    );
    let tree = component.toJSON();
    expect(tree).toMatchSnapshot();
  });

# leanpub-end-insert
});

๋‹ค์‹œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•ด ํ…Œ์ŠคํŠธ๊ฐ€ ์„ฑ๊ณต ๋˜๋Š” ์‹คํŒจํ–ˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. ํ…Œ์ŠคํŠธ๊ฐ€ ์„ฑ๊ณตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. App ์ปดํฌ๋„ŒํŠธ ๋‚ด ๋ Œ๋” ๋‚ด ์ถœ๋ ฅ์„ ๋ณ€๊ฒฝํ•˜๋ฉด ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค. ์Šค๋ƒ…์ƒท์„ ์—…๋ฐ์ดํŠธํ•˜๊ฑฐ๋‚˜ App ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

renderer.create() ํ•จ์ˆ˜๋Š” App ์ปดํฌ๋„ŒํŠธ ์Šค๋ƒ…์ƒท์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๊ฐ€์ƒ์œผ๋กœ ๋ Œ๋”๋”ฉ๋œ ์Šค๋ƒ…์ƒท์„ DOM์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ์‹คํ–‰ํ•œ ์Šค๋ƒ…์ƒท๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ DOM์ด ๋™์ผํ•˜๊ฒŒ ์œ ์ง€๋˜๊ณ  ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Œ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

์ง€๊ธˆ๋ถ€ํ„ฐ Search, Button, Table ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์ฒซ ๋ฒˆ์งธ๋กœ Search ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

{title="src/App.test.js",lang=javascript}

import React from 'react';
import ReactDOM from 'react-dom';
import renderer from 'react-test-renderer';
# leanpub-start-insert
import App, { Search } from './App';
# leanpub-end-insert

...

# leanpub-start-insert
describe('Search', () => {

  it('renders without crashing', () => {
    const div = document.createElement('div');
    ReactDOM.render(<Search>Search</Search>, div);
  });

  test('has a valid snapshot', () => {
    const component = renderer.create(
      <Search>Search</Search>
    );
    let tree = component.toJSON();
    expect(tree).toMatchSnapshot();
  });

});
# leanpub-end-insert

Search ์ปดํฌ๋„ŒํŠธ ๋‘ ํ…Œ์ŠคํŠธ๋Š” App ์ปดํฌ๋„ŒํŠธ์™€ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ํ…Œ์ŠคํŠธ๋Š” Search ์ปดํฌ๋„ŒํŠธ๋ฅผ DOM์— ๋ Œ๋”๋งํ•˜๊ณ  ๋ Œ๋”๋ง ํ”„๋กœ์„ธ์Šค ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ์—†๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์˜ค๋ฅ˜๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ, expect, match, equal ๋“ฑ ๊ฒ€์ฆ๋ฌธ(assertion)์ด ์—†์–ด๋„ ํ…Œ์ŠคํŠธ๊ฐ€ ์ค‘๋‹จ๋ฉ๋‹ˆ๋‹ค. ๋‘ ๋ฒˆ์งธ ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ๋Š” ๋ Œ๋”๋ง๋œ ์ปดํฌ๋„ŒํŠธ์˜ ์Šค๋ƒ…์ƒท์„ ์ €์žฅํ•˜๊ณ  ์ด์ „ ์Šค๋ƒ…์ƒท๊ณผ ๋น„๊ตํ•˜์—ฌ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์Šค๋ƒ…์ƒท์ด ๋ณ€๊ฒฝ๋˜๋ฉด ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

๋‘ ๋ฒˆ์งธ๋กœ Button ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. Search ์ปดํฌ๋„ŒํŠธ์™€ ๋™์ผํ•œ ํ…Œ์ŠคํŠธ ๊ทœ์น™์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

{title="src/App.test.js",lang=javascript}

...
# leanpub-start-insert
import App, { Search, Button } from './App';
# leanpub-end-insert

...

# leanpub-start-insert
describe('Button', () => {

  it('renders without crashing', () => {
    const div = document.createElement('div');
    ReactDOM.render(<Button>Give Me More</Button>, div);
  });

  test('has a valid snapshot', () => {
    const component = renderer.create(
      <Button>Give Me More</Button>
    );
    let tree = component.toJSON();
    expect(tree).toMatchSnapshot();
  });

});
# leanpub-end-insert

๋งˆ์ง€๋ง‰์œผ๋กœ Table ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. Table ์ปดํฌ๋„ŒํŠธ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ Œ๋”๋งํ•˜๊ธฐ ์œ„ํ•ด props๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

{title="src/App.test.js",lang=javascript}

...
# leanpub-start-insert
import App, { Search, Button, Table } from './App';
# leanpub-end-insert

...

# leanpub-start-insert
describe('Table', () => {

  const props = {
    list: [
      { title: '1', author: '1', num_comments: 1, points: 2, objectID: 'y' },
      { title: '2', author: '2', num_comments: 1, points: 2, objectID: 'z' },
    ],
  };

  it('renders without crashing', () => {
    const div = document.createElement('div');
    ReactDOM.render(<Table { ...props } />, div);
  });

  test('has a valid snapshot', () => {
    const component = renderer.create(
      <Table { ...props } />
    );
    let tree = component.toJSON();
    expect(tree).toMatchSnapshot();
  });

});
# leanpub-end-insert

์ด์ฒ˜๋Ÿผ ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ ์ž‘์„ฑ์€ ๋งค์šฐ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ ์ถœ๋ ฅ์ด ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๋Š”์ง€๋งŒ ํ™•์ธํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ผ๋‹จ ์ถœ๋ ฅ์„ ๋ณ€๊ฒฝํ•˜๋ฉด ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ˆ˜๋ฝํ• ์ง€ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์›ํ•˜๋Š” ์ถœ๋ ฅ์ด ์•„๋‹ ๊ฒฝ์šฐ, ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

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

  • render() ๋ฉ”์„œ๋“œ์—์„œ ์ปดํฌ๋„ŒํŠธ ๋ฆฌํ„ด ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋ฉด ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
    • ์Šค๋ƒ…์ƒท ๋ณ€๊ฒฝ์„ ์ˆ˜๋ฝํ•˜๊ฑฐ๋‚˜ ๊ฑฐ๋ถ€ํ•ฉ๋‹ˆ๋‹ค.
  • ์•ž์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜์—ฌ ์ตœ์‹  ์ƒํƒœ๋กœ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.

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

4.4 Enzyme ๋‹จ์œ„ ํ…Œ์ŠคํŠธ

Enzyme(์—”์ž์ž„)๋Š” ์—์–ด๋น„์•ค๋น„๊ฐ€ ๋งŒ๋“  ํ…Œ์ŠคํŠธ ์œ ํ‹ธ๋ฆฌํ‹ฐ๋กœ ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ™•์ธ, ์กฐ์ž‘, ํ†ต๊ณผ์‹œํ‚ต๋‹ˆ๋‹ค. Enzyme์€ ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ๋ฅผ ๋ณด์™„ํ•˜์—ฌ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

์ด์ œ Enzyme์„ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ฒซ์งธ, Enzyme์€ create-react-app์—์„œ ์ œ๊ณตํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋ณ„๋„๋กœ ์„ค์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ฆฌ์•กํŠธ ํ™•์žฅํŒ๋„ ํ•จ๊ป˜ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

{title="Command Line",lang="text"}

npm install --save-dev enzyme react-addons-test-utils enzyme-adapter-react-16

๋‘˜์งธ, Enzyme์„ ํ…Œ์ŠคํŠธ ์„ค์ •์— ์ถ”๊ฐ€ํ•˜๊ณ  ๋ฆฌ์•กํŠธ๊ฐ€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ์–ด๋Œ‘ํ„ฐ๋ฅผ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค.

{title="src/App.test.js",lang=javascript}

import React from 'react';
import ReactDOM from 'react-dom';
import renderer from 'react-test-renderer';
# leanpub-start-insert
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
# leanpub-end-insert
import App, { Search, Button, Table } from './App';

# leanpub-start-insert
Enzyme.configure({ adapter: new Adapter() });
# leanpub-end-insert

Table ์ปดํฌ๋„ŒํŠธ "describe" ๋ฌธ์— ์ฒซ ๋ฒˆ์งธ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. shallow() ๋ฉ”์„œ๋“œ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. ๋ฆฌ์ŠคํŠธ ๋‚ด ์•„์ดํ…œ์ด ๋‘ ๊ฐœ์ž„์œผ๋กœ Table ์ปดํฌ๋„ŒํŠธ ๋‚ด ์—˜๋ ˆ๋จผํŠธ๊ฐ€ ๋‘ ๊ฐœ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒ€์ฆ๋ฌธ์€ table-row ํด๋ž˜์Šค์— ๋‘ ์—˜๋ ˆ๋จผํŠธ๊ฐ€ ์žˆ๋Š”์ง€๋ฅผ ์ฒดํฌํ•ฉ๋‹ˆ๋‹ค.

{title="src/App.test.js",lang=javascript}

import React from 'react';
import ReactDOM from 'react-dom';
import renderer from 'react-test-renderer';
# leanpub-start-insert
import Enzyme, { shallow } from 'enzyme';
# leanpub-end-insert
import Adapter from 'enzyme-adapter-react-16';
import App, { Search, Button, Table } from './App';

...

describe('Table', () => {

  const props = {
    list: [
      { title: '1', author: '1', num_comments: 1, points: 2, objectID: 'y' },
      { title: '2', author: '2', num_comments: 1, points: 2, objectID: 'z' },
    ],
  };

  ...

# leanpub-start-insert
  it('shows two items in list', () => {
    const element = shallow(
      <Table { ...props } />
    );

    expect(element.find('.table-row').length).toBe(2);
  });
# leanpub-end-insert

});

shallow() ๋ฉ”์„œ๋“œ๋Š” ์ž์‹ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์—†๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ „๋‹ดํ•ด ํ…Œ์ŠคํŠธ๋ฅผ ์‹ค์‹œํ•ฉ๋‹ˆ๋‹ค.

Enzyme API์—๋Š” ์„ธ ๊ฐ€์ง€ ๋ Œ๋”๋ง ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. shallow() ๋ฉ”์„œ๋“œ๋ฅผ ํฌํ•จํ•ด mount()์™€ render() ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. mount()์™€ render() ๋ฉ”์„œ๋“œ๋Š” ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ ๋ฐ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ธ์Šคํ„ด์Šคํ™”ํ•ฉ๋‹ˆ๋‹ค. mount() ๋ฉ”์„œ๋“œ๋Š” ์ปดํฌ๋„ŒํŠธ ์ƒ๋ช…์ฃผ๊ธฐ ๋ฉ”์„œ๋“œ๋ฅผ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค. ๋ Œ๋”๋ง ๋ฉ”์„œ๋“œ ์ฃผ์š” ์‚ฌ์šฉ ๊ทœ์น™์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ์ œ์ผ ๋จผ์ € shallow() ํ…Œ์ŠคํŠธ๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
  • mount() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด componentDidMount() ๋˜๋Š” componentDidUpdate() ๋ฉ”์„œ๋“œ๋ฅผ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค.
  • ์ปดํฌ๋„ŒํŠธ์˜ ์ƒ๋ช…์ฃผ๊ธฐ์™€ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ…Œ์ŠคํŠธ ์‹œ, mount()๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • render() ๋ฉ”์„œ๋“œ๋Š” mount()๋ณด๋‹ค ์ ์€ ์˜ค๋ฒ„ํ—ค๋“œ(overhead, ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด ํˆฌ์ž…๋˜๋Š” ๊ฐ„์ ‘์ ์ธ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ยท๋ฉ”๋ชจ๋ฆฌ)๋กœ ์ž์‹ ๋ Œ๋”๋ง์„ ํ…Œ์ŠคํŠธํ•  ๊ฒฝ์šฐ, ์ƒ๋ช…์ฃผ๊ธฐ ๋ฉ”์„œ๋“œ์™€ ๋ฌด๊ด€ํ•  ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์•ž์œผ๋กœ๋„ ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๊ณ„์† ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ญ์ƒ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฐ„๋‹จํ•˜๊ณ  ์œ ์ง€ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๋งค๋ฒˆ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณ€๊ฒฝํ•  ๋•Œ๋งˆ๋‹ค ๋ฆฌํŒฉํ„ฐ๋งํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํŽ˜์ด์Šค๋ถ์ด Jest์—์„œ ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ๋ฅผ ๋„์ž…ํ•œ ์ด์œ ๋„ ์ด ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

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

  • Button ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•œ Enzyme ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ด…๋‹ˆ๋‹ค.
  • ๋‹ค์Œ ์ ˆ์—์„œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ตœ์‹  ์ƒํƒœ๋กœ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.

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

4.5 PropTypes ์ปดํฌ๋„ŒํŠธ ์ธํ„ฐํŽ˜์ด์Šค

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํƒ€์ž… ์ธํ„ฐํŽ˜์ด์Šค์ธ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ(TypeScript) ๋˜๋Š” ํ”Œ๋กœ์šฐ(Flow)์— ๋Œ€ํ•ด ํ•œ๋ฒˆ ์ฏค ๋“ค์–ด๋ณธ ์ ์ด ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ๋Š” ํ…์ŠคํŠธ ์ž…๋ ฅ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ์‰ฝ๊ธฐ ๋•Œ๋ฌธ์— ํ…์ŠคํŠธ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กœ๊ทธ๋žจ์ด ์‹คํ–‰๋˜๊ธฐ ์ „ ์—๋””ํ„ฐ์™€ ์œ ํ‹ธ๋ฆฌํ‹ฐ๋กœ ํ…์ŠคํŠธ ์˜ค๋ฅ˜๋ฅผ ์ฒดํฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋กœ์จ ํ”„๋กœ๊ทธ๋žจ ํ’ˆ์งˆ์„ ๊ฒฌ๊ณ ํ•˜๊ฒŒ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.

์ด ์ฑ…์—์„œ๋Š” ์ปดํฌ๋„ŒํŠธ ์œ ํ˜• ์ฒดํฌ๋ฅผ ์œ„ํ•ด PropType(ํ”„๋กญํƒ€์ž…)์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์™€ ํ”Œ๋กœ์šฐ๋Š” ๋‹ค๋ฃจ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ฆฌ์•กํŠธ๋Š” ํƒ€์ž… ๊ฒ€์‚ฌ๊ธฐ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. PropType๋Š” ์ปดํฌ๋„ŒํŠธ ์ธํ„ฐํŽ˜์ด์Šค์ž…๋‹ˆ๋‹ค. PropTypes ์ธํ„ฐํŽ˜์ด์Šค๋Š” ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—์„œ ํ•˜์œ„ ์ปดํผ๋„ŒํŠธ๋กœ ์ „๋‹ฌ๋˜๋Š” ๋ชจ๋“  props์˜ ์œ ํšจ๋ฅผ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฒˆ ์ ˆ์—์„œ๋Š” PropType์„ ์‚ฌ์šฉํ•ด ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ ํƒ€์ž…์„ ์•ˆ์ „ํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์›๋‹ˆ๋‹ค. ๋‹ค์Œ ์žฅ์— ๋ณ€๊ฒฝ๋  ๋‚ด์šฉ์€ ์ƒ๋žตํ•˜๊ณ  ์ง„ํ–‰ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ถˆํ•„์š”ํ•œ ์ฝ”๋“œ ๋ฆฌํŒฉํ„ฐ๋ง์ด ์ถ”๊ฐ€๋กœ ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋‹ค๋งŒ ์ปดํผ๋„ŒํŠธ ์ธํ„ฐํŽ˜์ด์Šค ํƒ€์ž…์„ ์•ˆ์ „ํ•˜๊ฒŒ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด PropType๋Š” ๊ณ„์† ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋จผ์ € PropType ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

{title="Command Line",lang="text"}

npm install prop-types

App.js ํŒŒ์ผ์—์„œ PropTypes๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

{title="src/App.js",lang=javascript}

# leanpub-start-insert
import PropTypes from 'prop-types';
# leanpub-end-insert

Button ์ปดํฌ๋„ŒํŠธ์— props ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ง€์ •ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

{title="src/App.js",lang=javascript}

const Button = ({
  onClick,
  className = '',
  children,
}) =>
  <button
    onClick={onClick}
    className={className}
    type="button"
  >
    {children}
  </button>

# leanpub-start-insert
Button.propTypes = {
  onClick: PropTypes.func,
  className: PropTypes.string,
  children: PropTypes.node,
};
# leanpub-end-insert

ํ•จ์ˆ˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜(function signature, ํ•จ์ˆ˜์™€ ๋ฉ”์„œ๋“œ ์ž…๋ ฅใ†์ถœ๋ ฅ์„ ์ •์˜) ์—์„œ ์ธ์ž๋ฅผ ๊ฐ€์ ธ์™€ PropTypes์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์ธ PropTypes ์ •์˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • PropTypes.array
  • PropTypes.bool
  • PropTypes.func
  • PropTypes.number
  • PropTypes.object
  • PropTypes.string

๋˜ํ•œ ๋ Œ๋”๋ง ์—˜๋ ˆ๋จผํŠธ(๋…ธ๋“œ, ๋ฌธ์ž์—ด, ๋ฆฌ์•กํŠธ ์—˜๋ ˆ๋จผํŠธ)๋ฅผ ์ •์˜ํ•˜๋Š” PropType๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • PropTypes.node
  • PropTypes.element

Button ์ปดํฌ๋„ŒํŠธ์—์„œ PropTypes.node๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ฆฌ์•กํŠธ ๊ณต์‹ ๋ฌธ์„œ์—์„œ ๋” ๋งŽ์€ PropType ์ •์˜๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Button ์ปดํฌ๋„ŒํŠธ์— PropTypes ์ •์˜๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ๊ฐ’์€ null ๋˜๋Š” undefined๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ค‘์š”ํ•œ props๋ผ๋ฉด PropTypes์„ ์ •์˜ํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋žŒ์งํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์ปดํฌ๋„ŒํŠธ์— props ์ „๋‹ฌ์„ ํ•„์ˆ˜ ์‚ฌํ•ญ์œผ๋กœ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

{title="src/App.js",lang=javascript}

Button.propTypes = {
# leanpub-start-insert
  onClick: PropTypes.func.isRequired,
# leanpub-end-insert
  className: PropTypes.string,
# leanpub-start-insert
  children: PropTypes.node.isRequired,
# leanpub-end-insert
};

className์˜ ๊ธฐ๋ณธ๊ฐ’์ด ๋นˆ ๋ฌธ์ž์—ด์ž„์œผ๋กœ ํ•„์ˆ˜ ์‚ฌํ•ญ์€ ์•„๋‹™๋‹ˆ๋‹ค. ์ด๋ฒˆ์—๋Š” Table ์ปดํฌ๋„ŒํŠธ PropType์„ ์ •์˜ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

{title="src/App.js",lang=javascript}

# leanpub-start-insert
Table.propTypes = {
  list: PropTypes.array.isRequired,
  onDismiss: PropTypes.func.isRequired,
};
# leanpub-end-insert

Table ์ปดํฌ๋„ŒํŠธ์˜ list props ๊ฐ์ฒด ๋‚ด ๋ชจ๋“  ํ”„๋กœํผํ‹ฐ๋ฅผ ์ •์˜ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

{title="src/App.js",lang=javascript}

Table.propTypes = {
  list: PropTypes.arrayOf(
    PropTypes.shape({
      objectID: PropTypes.string.isRequired,
      author: PropTypes.string,
      url: PropTypes.string,
      num_comments: PropTypes.number,
      points: PropTypes.number,
    })
  ).isRequired,
  onDismiss: PropTypes.func.isRequired,
};

์ด์ค‘ objectID ํ”„๋กœํผํ‹ฐ์˜ PropTypes ์ •์˜๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค. objectID์— ์˜์กดํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ํ”„๋กœํผํ‹ฐ๋Š” ํ™”๋ฉด์— ํ‘œ์‹œ๋งŒ ๋จ์œผ๋กœ ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”์šฑ์ด ํ•ด์ปค ๋‰ด์Šค API๊ฐ€ ์ •์˜๋œ ํ”„๋กœํผํ‹ฐ ํƒ€์ž…์ด ํ•ญ์ƒ ์œ ์ง€๋˜๋Š”์ง€ ํ™•์‹ ํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

PropTypes ์ด์™ธ์— prop์˜ ๊ธฐ๋ณธ๊ฐ’์„ ์ •์˜ํ•˜๋Š” default props์ด ์žˆ์Šต๋‹ˆ๋‹ค. Button ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. className ํ”„๋กœํผํ‹ฐ๋Š” ์ปดํฌ๋„ŒํŠธ ์‹œ๊ทธ๋‹ˆ์ฒ˜ ์•ˆ์— ES6 ๊ธฐ๋ณธ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

{title="src/App.js",lang=javascript}

const Button = ({
  onClick,
  className = '',
  children
}) =>
  ...

์ด ๊ธฐ๋ณธ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ default props์œผ๋กœ ๋ฐ”๊พธ๊ฒ ์Šต๋‹ˆ๋‹ค.

{title="src/App.js",lang=javascript}

const Button = ({
  onClick,
  className,
  children
}) =>
  <button
    onClick={onClick}
    className={className}
    type="button"
  >
    {children}
  </button>

Button.defaultProps = {
  className: '',
};

ES6 ๊ธฐ๋ณธ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ๋™์ผํ•˜๊ฒŒ default prop์€ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ฐ’์„ ์ง€์ •ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ, ํ”„๋กœํผํ‹ฐ ๊ธฐ๋ณธ ๊ฐ’์ด ์„ค์ •๋ฉ๋‹ˆ๋‹ค. default prop์„ ํ‰๊ฐ€ํ•œ ํ›„ PropType ํƒ€์ž… ๊ฒ€์‚ฌ๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•ด ์ปค๋งจ๋“œ ๋ผ์ธ์—์„œ ์ปดํฌ๋„ŒํŠธ PropType ์˜ค๋ฅ˜๊ฐ€ ์žˆ๋Š”์ง€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ์—์„œ PropType์„ isRequired๋กœ ์ •์˜ํ–ˆ์œผ๋‚˜, ์ปดํฌ๋„ŒํŠธ ๋‚ด ๋ชจ๋“  props๋ฅผ ์ •์˜ํ•˜์ง€ ์•Š์•˜์„ ๋•Œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ์ž์ฒด๋Š” ํ†ต๊ณผ๋ฉ๋‹ˆ๋‹ค. ์˜ค๋ฅ˜๋ฅผ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด ํ…Œ์ŠคํŠธ์—์„œ ํ•„์ˆ˜ props๋ฅผ ๋ชจ๋‘ ์ „๋‹ฌํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

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

  • Search ์ปดํฌ๋„ŒํŠธ์˜ PropType๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
  • ๋‹ค์Œ ์žฅ์—์„œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ถ”๊ฐ€, ์ˆ˜์ •ํ•  ๋•Œ๋งˆ๋‹ค PropType์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.

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

{pagebreak}

4.6 ์ •๋ฆฌํ•˜๋ฉด

4์žฅ์—์„œ๋Š” ์ฝ”๋“œ ๊ตฌ์„ฑ๊ณผ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ๋ฒ•์„ ๋ฐฐ์› ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด ๋‚ด์šฉ์„ ์ •๋ฆฌํ•ด๋ด…์‹œ๋‹ค.

  • ๋ฆฌ์•กํŠธ
    • PropTypes๋Š” ์ปดํฌ๋„ŒํŠธ ํƒ€์ž…์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
    • Jest๋Š” ์ปดํฌ๋„ŒํŠธ ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
    • Enzyme์€ ์ปดํฌ๋„ŒํŠธ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • ES6
    • import์™€ export๋กœ ์ฝ”๋“œ๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • ์ผ๋ฐ˜
    • ์ฝ”๋“œ ์กฐ์งํ™”๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

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