์ด๋ฒ ์ฅ์ ๊ท๋ชจ๊ฐ ํฐ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฝ๋ ์ ์ง์ ๊ด๋ฆฌ ๋ฐฉ๋ฒ์ ์์๋ณด๊ฒ ์ต๋๋ค. ์์ ๋ฅผ ํตํด ํด๋์ ํ์ผ ๊ตฌ์ฑ ๋ฐฉ๋ฒ๊ณผ ์กฐ์งํ๋ ์ฝ๋ ์์ฑ๋ฒ์ ๋ฐฐ์ฐ๊ณ , ๋ง์ง๋ง์ผ๋ก ํ ์คํธ ์ฝ๋ ์์ฑ๋ฒ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ์ํํธ์จ์ด ๊ฐ๋ฐ์ ์์ด ์ฝ๋ ํ์ง ํฅ์์ ๋งค์ฐ ์ค์ํฉ๋๋ค. ์ด๋ฒ ์ฅ์์๋ ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ์ ์ ์ ์ค๋จํฉ๋๋ค.
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 ํํ๋ก ์ค๊ณํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ ์ต๋๋ค. ๋ค์ ์ ์์ ํจ์๋ฅผ ๋ด๋ณด๋ด๊ณ ๊ฐ์ ธ์์ ํ ์คํธํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด๊ฒ ์ต๋๋ค.
๊ทธ๋์ 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 ์ ๋ฆฌํฉํ ๋งํ์ฌ ์ปดํฌ๋ํธ ๋ชจ๋๋ก ๋ง๋ญ๋๋ค.
์ด ์ฑ ์์๋ ํ ์คํธ๋ฅผ ๊น๊ฒ ๋ค๋ฃจ์ง ์์ต๋๋ค. ๊ทธ๋ฌ๋ ํ๋ก๊ทธ๋๋ฐ์์ ํ ์คํธ๋ ํ์์ด๊ธฐ ๋๋ฌธ์ ๊ผญ ์์์ผ ํฉ๋๋ค. ํ ์คํธ์ผ ๋ง๋ก ์ฝ๋์ ํ์ง์ ๋์ด๊ณ ๋ชจ๋ ๊ฒ์ด ๋ฌธ์ ์์ด ์ ์๋ํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ณด์ฅํ๊ธฐ ๋๋ฌธ์ด์ง์.
ํ ์คํ ํผ๋ผ๋ฏธ๋(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()
๋ฉ์๋์์ ์ปดํฌ๋ํธ ๋ฆฌํด ๊ฐ์ ๋ณ๊ฒฝํ๋ฉด ์ค๋ ์ท ํ ์คํธ๊ฐ ์คํจํ๋์ง ํ์ธํฉ๋๋ค.- ์ค๋ ์ท ๋ณ๊ฒฝ์ ์๋ฝํ๊ฑฐ๋ ๊ฑฐ๋ถํฉ๋๋ค.
- ์์ผ๋ก ์ปดํฌ๋ํธ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์ค๋ ์ท ํ ์คํธ ์ฝ๋๋ฅผ ์์ ํ์ฌ ์ต์ ์ํ๋ก ์ ์งํฉ๋๋ค.
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 ๋จ์ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํด๋ด ๋๋ค.
- ๋ค์ ์ ์์ ๋จ์ ํ ์คํธ๋ฅผ ์ต์ ์ํ๋ก ์ ์งํฉ๋๋ค.
์๋ฐ์คํฌ๋ฆฝํธ ํ์ ์ธํฐํ์ด์ค์ธ ํ์ ์คํฌ๋ฆฝํธ(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์ฅ์์๋ ์ฝ๋ ๊ตฌ์ฑ๊ณผ ํ ์คํธ ์ฝ๋ ์์ฑ๋ฒ์ ๋ฐฐ์ ์ต๋๋ค. ์ง๊ธ๊น์ง ๋ฐฐ์ด ๋ด์ฉ์ ์ ๋ฆฌํด๋ด ์๋ค.
- ๋ฆฌ์กํธ
- PropTypes๋ ์ปดํฌ๋ํธ ํ์ ์ ์ ์ํฉ๋๋ค.
- Jest๋ ์ปดํฌ๋ํธ ์ค๋ ์ท ํ ์คํธ๋ฅผ ์ํํฉ๋๋ค.
- Enzyme์ ์ปดํฌ๋ํธ ๋จ์ ํ ์คํธ๋ฅผ ์ํํฉ๋๋ค.
- ES6
import
์export
๋ก ์ฝ๋๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
- ์ผ๋ฐ
- ์ฝ๋ ์กฐ์งํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ํ์ฅํ ์ ์๋ ์ข์ ๋ฐฉ๋ฒ์ ๋๋ค.
์ค์ต ์ฝ๋๋ ๊นํ๋ธ ๋ฆฌํผ์งํ ๋ฆฌ์์ ํ์ธํ ์ ์์ต๋๋ค.