๋๋ ๋ฌธ์์ ๋จ์ ํ ์คํธ ์น์ ์ ์ฝ๊ณ ์์๊ณ ๋ฐ๋ณด ์ปดํฌ๋ํธ๋ฅผ ํ ์คํธํ๋ ๋ฐฉ๋ฒ์ ๋ํ ์์ ๊ฐ ์์ง๋ง "์ค๋งํธ ์ปดํฌ๋ํธ"(connect () ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ ์ปดํฌ๋ํธ)๋ฅผ ๋จ์ ํ ์คํธ ํ ์์๋ ๊ฒ์ ๋ค๋ฃจ์ง ์์ต๋๋ค. ์ค๋งํธ ๊ตฌ์ฑ ์์์ ๋จ์ ํ ์คํธ๋ connect ()๊ฐ ๋ง๋๋ ๋ํผ ๊ตฌ์ฑ ์์๋ก ์ธํด ์กฐ๊ธ ๋ ๋ณต์กํ๋ค๋ ๊ฒ์ด ๋ฐํ์ก์ต๋๋ค. ๋ฌธ์ ์ ์ผ๋ถ๋ connect ()๋ฅผ ํตํด ๊ตฌ์ฑ ์์๋ฅผ ๋ํํ๋ ค๋ฉด 'store'prop (๋๋ ์ปจํ ์คํธ)๊ฐ ์์ด์ผํ๋ค๋ ๊ฒ์ ๋๋ค.
๋๋ ์ด๊ฒ์ํ๋ ค๊ณ ๋ ธ๋ ฅํ๊ณ ๊ทธ๊ฒ์ ๋ฌ์ฑํ๋ ๋ ์ข์ ๋ฐฉ๋ฒ์ด ์๋์ง์ ๋ํ ์ฝ๊ฐ์ ํผ๋๋ฐฑ์ ๋ฐ๊ธฐ๋ฅผ ๋ฐ๋๋ค. ๊ทธ๋ฆฌ๊ณ ๋ด๊ฐ ํ ์ผ์ด ํฉ๋ฆฌ์ ์ผ๋ก ๋ณด์ด๋ฉด PR์ ๋ฐ์ด์ ์ด์ ๋ํ ์ ๋ณด๋ฅผ ๋จ์ ํ ์คํธ ๋ฌธ์์ ์ถ๊ฐ ํ ๊ฒ์ด๋ผ๊ณ ์๊ฐํ์ต๋๋ค.
์์ํ๋ ค๋ฉด ๋ฌธ์์ ๋จ์ ํ ์คํธ ์น์ ์์๋ ๊ธฐ์กด ์์ ๊ตฌ์ฑ ์์๋ฅผ ๊ฐ์ ธ ์์ connect ()๋ก ๋ํํ์ฌ ์ํ ๋ฐ ๋์คํจ์น ๋ฐ์ธ๋ฉ ๋ ์์ ์์ฑ์๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ์ต๋๋ค.
import React, { PropTypes, Component } from 'react';
import TodoTextInput from './TodoTextInput';
import TodoActions from '../actions/TodoActions';
import connect from 'redux-react';
class Header extends Component {
handleSave(text) {
if (text.length !== 0) {
this.props.addTodo(text);
}
}
render() {
return (
<header className='header'>
<h1>{this.props.numberOfTodos + " Todos"}</h1>
<TodoTextInput newTodo={true}
onSave={this.handleSave.bind(this)}
placeholder='What needs to be done?' />
</header>
);
}
}
export default connect(
(state) => {numberOfTodos: state.todos.length},
TodoActions
)(Header);
๋จ์ ํ ์คํธ ํ์ผ์์๋ ์์ ์ ๋น์ทํฉ๋๋ค.
import expect from 'expect';
import jsdomReact from '../jsdomReact';
import React from 'react/addons';
import Header from '../../components/Header';
import TodoTextInput from '../../components/TodoTextInput';
const { TestUtils } = React.addons;
/**
* Mock out the top level Redux store with all the required
* methods and have it return the provided state by default.
* <strong i="13">@param</strong> {Object} state State to populate in store
* <strong i="14">@return</strong> {Object} Mock store
*/
function createMockStore(state) {
return {
subscribe: () => {},
dispatch: () => {},
getState: () => {
return {...state};
}
};
}
/**
* Render the Header component with a mock store populated
* with the provided state
* <strong i="15">@param</strong> {Object} storeState State to populate in mock store
* <strong i="16">@return</strong> {Object} Rendered output from component
*/
function setup(storeState) {
let renderer = TestUtils.createRenderer();
renderer.render(<Header store={createMockStore(storeState)} />);
var output = renderer.getRenderedOutput();
return output.refs.wrappedInstance();
}
describe('components', () => {
jsdomReact();
describe('Header', () => {
it('should call call addTodo if length of text is greater than 0', () => {
const output = setup({
todos: [1, 2, 3]
});
var addTodoSpy = expect.spyOn(output.props, 'addTodo');
let input = output.props.children[1];
input.props.onSave('');
expect(addTodoSpy.calls.length).toBe(0);
input.props.onSave('Use Redux');
expect(addTodoSpy.calls.length).toBe(1);
});
});
});
๊ด๋ จ ๋ถ๋ถ ๋ง ํ์ํ๊ธฐ ์ํด์ด ํ
์คํธ๋ฅผ ์ฝ๊ฐ ๋จ์ํํ์ง๋ง ํ์คํ์ง ์์ ์์ ์ createMockStore
๋ฉ์๋์
๋๋ค. ํค๋ ์ปดํฌ๋ํธ๋ฅผ ์ํ์์ด ๋ ๋๋งํ๋ ค๊ณ ํ๋ฉด Redux (๋๋ react-redux)์์ ํด๋น ์ปดํฌ๋ํธ์ store
prop ๋๋ ์ปจํ
์คํธ๊ฐ ์์ด์ผํ๋ค๋ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค. <Provider>
๊ตฌ์ฑ ์์. ๋จ์ ํ
์คํธ์ ์ฌ์ฉํ๊ณ ์ถ์ง ์๊ธฐ ๋๋ฌธ์ ๋ชจ์ ๋ฐฉ๋ฒ์ ๋ง๋ค๊ณ ํ
์คํธ๊ฐ ์คํ ์ด์์ ์ค์ ํ๋ ค๋ ์ํ๋ก ํต๊ณผํ๋๋ก ํ์ฉํ์ต๋๋ค.
์ด ์ ๊ทผ ๋ฐฉ์์์ ๋ณผ ์์๋ ์ด์ ์ ๊ตฌ์ฑ ์์ ๋ด์์ ํจ์๋ฅผ ํ
์คํธ ํ ์์์๋ฟ๋ง ์๋๋ผ connect ()๋ก ์ ๋ฌํ๋ ๋ฉ์๋์ ๊ธฐ๋ฅ๋ ํ
์คํธ ํ ์ ์๋ค๋ ๊ฒ์
๋๋ค. expect(output.props.numberOfTodos).toBe(3)
mapStateToProps
ํจ์๊ฐ ๋ด๊ฐ ์์ ํ๋๋ก ์๋ํ๋์ง ํ์ธํ๋ expect(output.props.numberOfTodos).toBe(3)
์ ๊ฐ์ ๋ค๋ฅธ ์ฃผ์ฅ์ ์ฌ๊ธฐ์ ์ฝ๊ฒ ์์ฑํ ์ ์์ต๋๋ค.
๊ทธ๊ฒ์ ์ฃผ๋ ๋จ์ ์ ๋ด๊ฐ Redux ์คํ ์ด๋ฅผ ์กฐ๋กฑํด์ผํ๋ค๋ ๊ฒ์ธ๋ฐ, ๊ทธ๋ ๊ฒ ๋ณต์กํ์ง๋ ์์ง๋ง ๋ด๋ถ Redux ๋ก์ง์ ์ผ๋ถ์ธ ๊ฒ์ฒ๋ผ ๋๊ปด์ง๊ณ ๋ณ๊ฒฝ ๋ ์ ์์ต๋๋ค. ๋ถ๋ช ํ ๋ด ๋จ์ ํ ์คํธ๋ฅผ ์ํด ์ด๋ฌํ ๋ฉ์๋๋ฅผ ์ผ๋ฐ ๋จ์ ํ ์คํธ ์ ํธ๋ฆฌํฐ ํ์ผ๋ก ์ฎ๊ฒผ์ผ๋ฏ๋ก ์ ์ฅ์ ๋ฉ์๋๊ฐ ๋ณ๊ฒฝ๋๋ฉด ์ฝ๋๋ฅผ ํ๊ณณ์์ ์์ ํ๋ฉด๋ฉ๋๋ค.
์๊ฐ? ๋ค๋ฅธ ์ฌ๋์ด ์ค๋งํธ ๊ตฌ์ฑ ์์๋ฅผ ๋จ์ ํ ์คํธํ์ฌ ๋ ๋์ ๋ฐฉ๋ฒ์ ์ฐพ์์ต๋๊น?
๋์์ (์ฅ์๋์ง ์์) Header ํด๋์ค๋ฅผ ๋ด๋ณด๋ด๋ ๊ฒ์ด๋ฏ๋ก ๋ณ๋๋ก ๊ฐ์ ธ ์์ ํ ์คํธ ํ ์ ์์ต๋๋ค.
๋ํ ์ต์ํ Mocha๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ๋ชจ๋ ํ์ผ์์ jsdomReact ํธ์ถ์ด ํ์ํ์ง ์์ต๋๋ค. ๋ด setup.js๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
import jsdom from 'jsdom';
import ExecutionEnvironment from 'react/lib/ExecutionEnvironment';
if (!global.document || !global.window) {
global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = document.defaultView;
global.navigator = window.navigator;
ExecutionEnvironment.canUseDOM = true;
window.addEventListener('load', () => {
console.log('JSDom setup completed: document, window and navigator are now on global scope.');
});
}
--require ๋ช
๋ น ์ค ์ต์
์ ํตํด๋ก๋๋ฉ๋๋ค : mocha -r babelhook -r test/setup --recursive
(babelhook์ require('babel-core/register')
๋ํ ํธ์ถ์
๋๋ค).
์, Header ํด๋์ค์ ๋ฐ์ฝ ๋ ์ดํ
๋ ํด๋์ค๋ฅผ ๋ชจ๋ ๋ด๋ณด๋ผ ์ ์์ง๋ง ๋จ์ ํ
์คํธ๋ฅผ ์ํด ์์ค ์ฝ๋๋ฅผ ๋ณ๊ฒฝํ์ง ์์๋๋๊ธฐ๋ฅผ ๋ฐ๋์ต๋๋ค. ๊ทธ๋ ๊ฒํ๋ฉด ๋ฐ์ฝ ๋ ์ดํ
๋ ๊ตฌ์ฑ ์์๊ฐ ํฌํจ ๋ ๋ชจ๋ ์์น๋ฅผ ๋ณ๊ฒฝํด์ผํฉ๋๋ค (์ : import Header from
. / components / Header` ๋์ import {DecoratedHeader} from './components/Header'
).
jsdom ์ค์ ์ ๊ดํด์๋ ์ฌ์ฉ ์ฌ๋ก๋ฅผ ๋ณด์ฌ์ฃผ๋ ๋ฐฉ๋ฒ์ผ๋ก ๋ฌธ์์ ์์ ๋ฅผ ์์ ํ ๋ณต์ฌํ๊ณ ์์์ผ๋ฉฐ, ๊ธฐ๋ณธ ์ค์ ์ผ๋ก ์ฌ์ฉํ์ง ์๊ณ ๋จ์ง ์์ผ๋ฟ์ ๋๋ค.
์์ ์ฝ๋ ์ํ์ด ๋ถ์์ ํ๋ค๋ ๊ฒ์ ์์์ต๋๋ค. ์ค๋งํธ ๊ตฌ์ฑ ์์๋ฅผ ์ง์ ํ
์คํธํ๋ ค๋ฉด ๋ ๋๋ง ๊ฒฐ๊ณผ ๋์ refs.wrappedInstance
๋ฅผ ๋ฐํํ๋ ์ ํธ๋ฆฌํฐ ๋ฉ์๋๊ฐ ์์ด์ผ ์ฐ๊ฒฐ ์ฅ์ ๊ตฌ์ฑ ์์ (์์ ์ค์ ํจ์์์ ์
๋ฐ์ดํธ ๋จ)๊ฐ ์ ๊ณต๋ฉ๋๋ค. . ์ด๊ฒ์ ๋ค์ ์ฝ๊ฐ์ ๋จ์ ํ
์คํธ๊ฐ Redux ๋ด๋ถ (์ด ๊ฒฝ์ฐ, ํนํ react-redux ๋ด๋ถ)์ ์์กดํ๊ฒ ๋ง๋ญ๋๋ค. ๊ทธ๋์ ์๋ํ์ง๋ง ์ฝ๊ฐ ํ์ฝํ๋ค๊ณ ๋๋๋๋ค.
@ernieturner ๋ํ์ด๋ฅผ ์ํด getWrappedInstance()
๊ณต์ฉ API๋ฅผ ์ ๊ณตํ๋ฏ๋ก refs
์ง์ ์ก์ธ์คํ๋ ๊ฒ์ด ๊ฑฑ์ ๋๋ ๊ฒฝ์ฐ ์ ๋ขฐํ ์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๊ฒ์ ํฌ๋ช
ํ๊ฒ ๋ง๋๋ react-test-tree ์ ๊ฐ์ ์ ํธ๋ฆฌํฐ๋ ์์ต๋๋ค.
@ernieturner @ghengeveld๊ฐ ์๋ฏธํ๋ ๋ฐ๋ ์ด๋์์๋ ES6 ๋ชจ๋์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ๋ฐ์ฝ ๋ ์ดํ ๋ Header ๊ตฌ์ฑ ์์๋ฅผ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ๋ด๋ณด๋ผ ์ ์์ผ๋ฉฐ ์ผ๋ฐ React ๊ตฌ์ฑ ์์๋ ์ถ๊ฐ ์ด๋ฆ ๋ด๋ณด๋ด๊ธฐ๊ฐ ๋ ์ ์๋ค๋ ๊ฒ์ ๋๋ค. ์๋ฅผ ๋ค๋ฉด-
//header.js
export class HeaderDumbComponent {
render() {
return <header><div>...</div></header>;
}
}
export default connect(
(state) => {numberOfTodos: state.todos.length},
TodoActions
)(HeaderDumbComponent);
์ด ์ด์ค ๋ด๋ณด๋ด๊ธฐ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฉ์ธ ์ฑ์์ import Header from './header.js'
๋ก ์ด์ ๊ณผ ๊ฐ์ด ์ค๋งํธ ๊ตฌ์ฑ ์์๋ฅผ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ ํ
์คํธ๋ import {HeaderDumbComponent} from '../components/header.js'
์ง์ redux๋ฅผ ์ฐํํ๊ณ ํต์ฌ ๊ตฌ์ฑ ์์์ ๋์์ ํ
์คํธ ํ ์ ์์ต๋๋ค.
@ eugene1g @ghengeveld ์ด๊ฒ์ ์ค์ ๋ก ๋ฌธ์ ์ ๋ํ ์ ๋ง ์ข์ ํด๊ฒฐ์ฑ ์ ๋๋ค. ๊ทธ๊ฒ์ ์ค๋ช ํ๋ "Writing tests"๋ฌธ์์ PR์ ๋ณด๋ด์ญ์์ค!
@ eugene1g ๊ทธ๊ฒ ๋ฐ๋ก ์ ๊ฐ ์๋ฏธํ๋
@gaearon ๋ด๊ฐ ํ ๊ฒ.
@ghengeveld ์คํด์ ๋ํด ์ฌ๊ณผ๋๋ฆฝ๋๋ค. ๋๋ ์ฌ์ ํ CommonJS ์คํ์ผ ๊ฐ์ ธ ์ค๊ธฐ๋ฅผ ์ฌ์ฉํ๊ณ ์์ผ๋ฏ๋ก ์ฌ์ ํ ES6 ๋ชจ๋์์ ๊ฝค ๋ น์ฌ ์์ต๋๋ค. ๋ฌธ์์ ๊ณต๊ฐํ๊ธฐ์ ์ข์ ์๋ฃจ์ ์ธ ๊ฒ ๊ฐ์ต๋๋ค. ๋ํ mapStateToProps / mapDispatchToProps ๋ฉ์๋๋ฅผ ๋ ธ์ถํ์ฌ ์์ ์ ์กฐ๋กฑํ์ง ์๊ณ ๋ ํ ์คํธ ํ ์ ์๋๋ก ํ ์ ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.
export class HeaderDumpComponent ...
export function mapStateToProps(state) ...
export function mapDispatchToProps(dispatch) ...
export default connect(mapStateToProps, mapDispatchToProps)(HeaderDumpComponent);
๋๋ถ๋ถ์ ๋งต ๋ฐฉ๋ฒ์ ๋จ์ ํ ์คํธ๊ฐ ํ์ํ์ง ์์ ์ ๋๋ก ๊ฐ๋จ ํ ์ ์์ง๋ง, ์ผ๋ถ๋ ๋ด ๊ตฌ์ฑ ์์์ ๊ธฐ๋ฅ์์ ๋งค์ฐ ์ค์ํ ๋ถ๋ถ์ด๋ฏ๋ก ํ์ธํ ๊ฐ์น๊ฐ ์์ต๋๋ค.
๋ชจ๋ ์ฌ๋์๊ฒ ์ ์ ํด ์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค. ๋ฌธ์์ ๋ํ ๋์์ด ํ์ํ๋ฉด ์๋ ค์ฃผ์ญ์์ค.
์ง๊ธ ๋น์ฅ ์์ ์ค๋ช
๋๋๋ก ๊ถ์ฅ๋๋ ์ ๊ทผ ๋ฐฉ์์ ์๋ํ๊ณ ์์ง๋ง ์์ ํ ํธ์ํ์ง ์์ต๋๋ค. ์ด๋ ๊ฒํ๋ฉด connect
์ ๋ฌ ๋ ๋งค๊ฐ ๋ณ์๊ฐ ํตํฉ ํ
์คํธ ์ธ๋ถ์์ ํ
์คํธ๋์ง ์์ ์ํ๋ก ๋จ์ต๋๋ค. ๋๋ ๋ชจ์ ์์ ์ด ์ ์ฌ์ ์ผ๋ก ๋ถ์ ์ง ์ ์๋ค๋ ๊ฒ์ ์ดํดํ์ง๋ง, ์ด๊ฒ์ด ์ปดํฌ๋ํธ ํ
์คํธ๋ฅผ ์ง์ํ ์ ์๋ค๋ฉด redux-mock-store ์ ๊ฐ์ ๊ฒ์ด ๋ฐ์ด๋ค ์์๋ ๊ณณ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค. ๊ทธ ๋ฐฉํฅ์ ๋ํด ์ด๋ป๊ฒ ์๊ฐํ์ญ๋๊น?
๋ฟก๋ฟก
์ผ๋ฐ ์์ ์ ์ฌ์ฉํ๊ณ ํน์ ์ด๊ธฐ ์ํ๋ก ์๋ถ์ ๊ณต๊ธํ์ง ์๋ ์ด์ ๋ ๋ฌด์์
๋๊น?
์ด๋ค ์์
์ด ์ ๋ฌ๋๋์ง ํ
์คํธํ๋ ค๋ ๊ฒฝ์ฐ์๋ง ์คํ ์ด๋ฅผ ๋ชจ์ํ๋ฉด๋ฉ๋๋ค.
๋ฟก๋ฟก
์ด ์ ์ฅ์์์ counter
๋ฐ todomvc
์์ ์ ๋ํ ๋จ์ ํ
์คํธ๋ฅผ ์ฐธ์กฐํ์ธ์.
๋ค๋ฅธ ๊ตฌ์ฑ ์์ ๋ด๋ถ์์ ์ค๋งํธ ๊ตฌ์ฑ ์์๋ฅผ ๋ ๋๋งํ๊ณ ์์ ๋ ๋๋ฌ๋ฅผ ์ฌ์ฉํ ์์๋ ๋ฌธ์ ๊ฐ์์์ต๋๋ค (๊ตฌ์ฑ ์์๋ componentDid * ๋ฉ์๋๋ฅผ ์ฌ์ฉํจ). ์ฐ๋ฆฌ๊ฐ ํ ๊ฒ์ ๊ฐ๋จํ React ์ปดํฌ๋ํธ๋ฅผ ๋ฐํํ๋ ํจ์๋ฅผ ๋ฐํํ๊ธฐ ์ํด connect ํจ์๋ฅผ ์คํ (sinon ์ฌ์ฉ)ํ๋ ๊ฒ์ ๋๋ค.
๋ถ์์ง๊ธฐ ์ฌ์ฐ ๋ฉฐ React 0.14๋ก ๋ง์ด๊ทธ๋ ์ด์ ํ ์์๊ฒ๋๋ฉด ์์ ๋ ๋๋ฌ๋ก ์ด๋ํ๋ ค๊ณ ํ์ง๋ง์ด ๋ฐฉ๋ฒ์ ๋น๋ถ๊ฐ ํ ์คํธ์์ ์ฐจ๋จ์ ํด์ ํ์ต๋๋ค.
์ด๊ฒ์๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ๋๋ React์ Redux ๋ชจ๋์ ๋ค์ ์ต์ํ์ง ์์ผ๋ฏ๋ก ์ด๊ฒ์ด ๋๋ฅผ ๋ฐฉํดํ๋ ๊ฒ ๊ฐ์ต๋๋ค. ์ด๋ป๊ฒ ๋์ ๋ค๋ ์ ์์์ต๋๊น? ๋ฟก๋ฟก
์ ์ ๋ ์ด์ค ๋ด๋ณด๋ด๊ธฐ ๋ฐฉ๋ฒ์ select (mapStoreToState) ํจ์๋ฅผ ํ ์คํธํ์ง ์์ ์ํ๋ก ๋ก๋๋ค. ๋ ๋ฆฝ์ ์ผ๋ก ํ ์คํธํ๋ ค๋ฉด ๋ด๋ณด๋ด๊ธฐ๋ํด์ผํ์ง๋ง ํ ์คํธ ์ด๋ฆ์ ๋ ๋ณ๊ฒฝํด์ผํฉ๋๋ค.
๋๋ shallowRenderer๊ฐ ์ฐ๊ฒฐ์ ๋ํ ๋ ๊ตฌ์ฑ ์์์ ํจ๊ป ์๋ํ๋๋กํ๋ ๋ฐฉ๋ฒ์ ์ฐพ๋ ๋ฐ ๊ด์ฌ์ด ์์ต๋๋ค. ๋ด ํ์ฌ ๋ฌธ์ ๋ ์์ ๋ ๋๋ฌ๋ฅผ ์ฌ์ฉํ ๋ ์์๋๋ Connect ๊ตฌ์ฑ ์์ ๋ง ์ ๋ฌํ๋ค๋ ๊ฒ์ ๋๋ค.
์ ์ ๋ ์ด์ค ๋ด๋ณด๋ด๊ธฐ ๋ฐฉ๋ฒ์ select (mapStoreToState) ํจ์๋ฅผ ํ ์คํธํ์ง ์์ ์ํ๋ก ๋ก๋๋ค. ๋ ๋ฆฝ์ ์ผ๋ก ํ ์คํธํ๋ ค๋ฉด ๋ด๋ณด๋ด๊ธฐ๋ํด์ผํ์ง๋ง ํ ์คํธ ์ด๋ฆ์ ๋ ๋ณ๊ฒฝํด์ผํฉ๋๋ค.
์๊ฒฉํ "ํ
์คํธ์ ์ด๋ฆ์ผ๋ก"๋ ์๋๋๋ค. ์ค์ ๋ก ๋ฆฌ๋์์ ํจ๊ป ์ ํ๊ธฐ ํจ์ (๊ฒฐ๊ตญ mapStateToProps
๊ฐ ๋ฌด์์ธ์ง)๋ฅผ ์ ์ํ๊ณ ํจ๊ป ํ
์คํธํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ์ด ๋ก์ง์ UI์ ๋ฌด๊ดํ๋ฉฐ ๋ฒ๋ค๋ก ์ ๊ณต ํ ํ์๊ฐ ์์ต๋๋ค. ์ผ๋ถ ์ ํ๊ธฐ์ ๋ํ shopping-cart
์์ ๋ฅผ ์ดํด๋ณด์ญ์์ค.
๊ทธ๋์ @gaearon ,์ด _selector_ ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ์ํ์์ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ๊ฒ์ ์ ์ํฉ๋๊น? ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ์ฌ๋๋ค์ ์ํ์์ ์ฌ๋ฌ ์์ฑ์ ์ฝ๊ณ ์ปดํฌ๋ํธ์ ์ํ์ ํ ๋นํ๊ธฐ ๋๋ฌธ์ ๋ถํ์ํ ์ค๋ฒ ํค๋๊ฐ ๋ง์ด ๋ฐ์ํ์ง ์์ต๋๊น?
์, Redux์ ๋ํ ์ผ๋ฐ์ ์ธ ์ ์ ํจํด์ state.some.nested.field
์ง์ ์ก์ธ์คํ๋ ๋์ ๊ฑฐ์ ๋ชจ๋ ๊ณณ์์ ์ ํ๊ธฐ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ๊ฒ์
๋๋ค. ๋งค์ฐ ๊ฐ๋จํ "์ผ๋ฐ"ํจ์์ผ ์ ์์ง๋ง ๊ฐ์ฅ ์ผ๋ฐ์ ์ผ๋ก ๋ฉ๋ชจ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ Reselect ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ํจ๊ป ๊ฒฐํฉ๋ฉ๋๋ค.
์ถ๊ฐ ์ค๋ฒ ํค๋๊ฐ ๋ฐ์ํ๋ ์ด์ ๋ ๋ฌด์์ ๋๊น?
์ฌ๊ธฐ์ ์ค๋ช
๋๋๋ก ์ด์ค ๋ด๋ณด๋ด๊ธฐ๋ฅผ ์ํํ์ง๋ง connect
์์ค๋ฅผ ์ฝ๊ณ "Container"๊ฐ WrappedComponent
์ ์ ์์ฑ์ "dumb"๊ตฌ์ฑ ์์์ ๋ํ ์ฐธ์กฐ๋ฅผ ์ ์งํ๋ค๋ ๊ฒ์ ๊นจ๋ฌ์์ต๋๋ค.
๊ทธ๋์ ๋์ :
// header.js
export const HeaderDumbComponent = (props) => <header><div>...</div></header>
export default connect(mapStateToProps)(HeaderDumbComponent)
// header.spec.js
import { HeaderDumbComponent } from './header'
it('renders', () => {
expect(<HeaderDumbComponent />).to.not.be.null
})
"dumb"๋ฒ์ ์ด ํ์ํ ๊ฒฝ์ฐ WrappedComponent
๋ฅผ ์ฌ์ฉํ์ฌ ์ด์ค ๋ด๋ณด๋ด๊ธฐ๋ฅผ ๋ฐฉ์ง ํ ์ ์์ต๋๋ค.
// header.js
const HeaderDumbComponent = (props) => <header><div>...</div></header>
export default connect(mapStateToProps)(HeaderDumbComponent)
// header.spec.js
import Header from './header'
it('renders', () => {
expect(<Header.WrappedComponent />).to.not.be.null
})
@gaearon- WrappedComponent
๋ ๋ฌธ์์์๋ ๊ฒ์ ๊ฐ์ํ ๋ ๊ฝค ์์ ์ ์ธ ์์ฑ ์ธ ๊ฒ ๊ฐ์ต๋๋ค. ์ด๋ค ์ด์ ๋ก ๋ ์ด๋ฐ ๋ฐฉ์์ผ๋ก ์ฌ์ฉํ์ง ๋ง๋ผ๊ณ ์กฐ์ธ ํ์๊ฒ ์ต๋๊น?
๋ด๊ฐํ๋ ์ผ์ ์ธ ๋ํ ๋ ๊ตฌ์ฑ ์์๋ฅผ ๋ด๋ณด๋ด๊ณ mapStateToProps ๋ฐ mapDispatchToProps๋ฅผ ๋ด๋ณด๋ด ํด๋น ํจ์๋ฅผ ๋จ์ ํ ์คํธ ํ ์ ์๋๋กํ๋ ๊ฒ์ ๋๋ค. ์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
import {ManageCoursePage, mapStateToProps, mapDispatchToProps} from './ManageCoursePage';
describe("mapStateToProps", () => {
function setup(courses, authors, id) {
const state = {
authors: authors
};
const ownProps = {
params: {
id: id
}
};
return mapStateToProps(state, ownProps);
}
it('sets the course when a course id is set', () => {
const courses = [ { id: "1", foo: "bar" }, { id: "2", foo: "notbar" } ];
const authors = [];
const id = "1";
const props = setup(courses, authors, id);
expect(props.course).toBe(courses[0]);
});
it('sets the course when a course id is not set', () => {
const courses = [ { id: "1", foo: "bar" }, { id: "2", foo: "notbar" } ];
const authors = [];
const props = setup(courses, authors, null);
expect(props.course).toEqual({});
});
it('sets the course when a course id is set that is not present in the courses list', () => {
const courses = [ { id: "1", foo: "bar" }, { id: "2", foo: "notbar" } ];
const authors = [];
const id = "42";
const props = setup(courses, authors, null);
expect(props.course).toEqual({});
});
it('sets the authors formatted for a drop down list', () => {
const courses = [];
const authors = [ { id: "1", name: "John" }, { id: "2", name: "Jill" }];
const props = setup(courses, authors, null);
expect(props.authors).toEqual([
{ value: "1", text: "John" },
{ value: "2", text: "Jill" }
])
});
});
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
@ernieturner @ghengeveld๊ฐ ์๋ฏธํ๋ ๋ฐ๋ ์ด๋์์๋ ES6 ๋ชจ๋์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ๋ฐ์ฝ ๋ ์ดํ ๋ Header ๊ตฌ์ฑ ์์๋ฅผ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ๋ด๋ณด๋ผ ์ ์์ผ๋ฉฐ ์ผ๋ฐ React ๊ตฌ์ฑ ์์๋ ์ถ๊ฐ ์ด๋ฆ ๋ด๋ณด๋ด๊ธฐ๊ฐ ๋ ์ ์๋ค๋ ๊ฒ์ ๋๋ค. ์๋ฅผ ๋ค๋ฉด-
์ด ์ด์ค ๋ด๋ณด๋ด๊ธฐ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฉ์ธ ์ฑ์์
import Header from './header.js'
๋ก ์ด์ ๊ณผ ๊ฐ์ด ์ค๋งํธ ๊ตฌ์ฑ ์์๋ฅผ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ ํ ์คํธ๋import {HeaderDumbComponent} from '../components/header.js'
์ง์ redux๋ฅผ ์ฐํํ๊ณ ํต์ฌ ๊ตฌ์ฑ ์์์ ๋์์ ํ ์คํธ ํ ์ ์์ต๋๋ค.