ããã¥ã¡ã³ãã®åäœãã¹ãã®ã»ã¯ã·ã§ã³ãèªãã§ããŠããã ã³ã³ããŒãã³ãããã¹ãããæ¹æ³ã®äŸããããŸããããã¹ããŒãã³ã³ããŒãã³ããïŒconnectïŒïŒã¡ãœããã䜿çšããã³ã³ããŒãã³ãïŒãåäœãã¹ãã§ããããšã«ã€ããŠã¯èª¬æããŠããŸããã connectïŒïŒãäœæããã©ãããŒã³ã³ããŒãã³ãã®ãããã¹ããŒãã³ã³ããŒãã³ãã®åäœãã¹ãã¯å°ãè€éã§ããããšãããããŸããã åé¡ã®äžéšã¯ãconnectïŒïŒãä»ããŠã³ã³ããŒãã³ããã©ããããã«ã¯ããã¹ãã¢ãããããïŒãŸãã¯ã³ã³ããã¹ãïŒãå¿ èŠã«ãªãããšã§ãã
ç§ã¯ãããããããšããŠã²ã³ãå ¥ããŸããããããŠãããéæããããã®ããè¯ãæ¹æ³ããããã©ããã«ã€ããŠå°ããã£ãŒãããã¯ãåŸãããšãæãã§ããŸããã ãããŠãç§ããã£ãããšãåççã«èŠããããšã«ãªã£ãå Žåãç§ã¯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
ã®åã§ããããšãæ³å®ãããŠãããããå°éå
·ãŸãã¯ã³ã³ããã¹ããå¿
èŠã§ããããšã瀺ãããŸãã <Provider>
ã³ã³ããŒãã³ãã åäœãã¹ãã«ã¯äœ¿çšããããªãã®ã§ãã¢ãã¯ã¢ãŠãããŠãã¹ãã¢ã«èšå®ãããç¶æ
ã§ãã¹ãã«åæ Œã§ããããã«ããã¡ãœãããäœæããŸããã
ãã®ã¢ãããŒãã®å©ç¹ã¯ãã³ã³ããŒãã³ãå
ã®é¢æ°ããã¹ãã§ããã ãã§ãªããconnectïŒïŒã«æž¡ãã¡ãœããã®æ©èœããã¹ãã§ããããšã§ãã expect(output.props.numberOfTodos).toBe(3)
ãããªããšãè¡ãå¥ã®ã¢ãµãŒã·ã§ã³ãããã«ç°¡åã«èšè¿°ããŠã mapStateToProps
é¢æ°ãæåŸ
ã©ããã«åäœããŠããããšã確èªã§ããŸãã
ãã®äž»ãªæ¬ ç¹ã¯ãReduxã¹ãã¢ãã¢ãã¯ã¢ãŠãããå¿ èŠãããããšã§ããããã¯ããã»ã©è€éã§ã¯ãããŸããããå éšã®Reduxããžãã¯ã®äžéšã§ãããå€æŽãããå¯èœæ§ãããããã«æããŸãã æããã«ãç§ã®åäœãã¹ãã§ã¯ããããã®ã¡ãœãããäžè¬çãªåäœãã¹ããŠãŒãã£ãªãã£ãã¡ã€ã«ã«ç§»åããã®ã§ãã¹ãã¢ã¡ãœãããå€æŽãããå Žåã§ããã³ãŒãã1ãæã§å€æŽããã ãã§æžã¿ãŸãã
èãïŒ ä»ã®èª°ããã¹ããŒãã³ã³ããŒãã³ãã®ãŠããããã¹ããè©Šããç©äºãè¡ãããã®ããè¯ãæ¹æ³ãèŠã€ããŸãããïŒ
å¥ã®æ¹æ³ã¯ãïŒè£ 食ãããŠããªãïŒããããŒã¯ã©ââã¹ããšã¯ã¹ããŒãããããšã§ããããã«ãããåå¥ã«ã€ã³ããŒãããŠãã¹ãã§ããŸãã
ãŸããå°ãªããšã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ã¢ãžã¥ãŒã«ã䜿çšããå Žåãè£ é£ŸãããããããŒã³ã³ããŒãã³ããããã©ã«ããšããŠãšã¯ã¹ããŒãã§ãããã¬ãŒã³ãª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'
以åãšåãããã«ã¹ããŒãã³ã³ããŒãã³ããæ¶è²»ã§ããŸããããã¹ãã§ã¯reduxããã€ãã¹ããŠãã³ã¢ã³ã³ããŒãã³ãã®åäœãimport {HeaderDumbComponent} from '../components/header.js'
çŽæ¥ãã¹ãã§ããŸãã
@ eugene1g @ghengeveldããã¯å®éã«ã¯åé¡ã«ã¯æ¬åœã«è¯ã解決çã§ãã ããã説æãããã©ã€ãã£ã³ã°ãã¹ããããã¥ã¡ã³ãã«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ã®ãããªãã®ãé£ã³èŸŒãå¯èœæ§ããããšæããŸãã ãã®æ¹åæ§ã«ã€ããŠã©ãæããŸããïŒ
@carpeliam
æ®éã®åºã䜿ã£ãŠãç¹å®ã®åæç¶æ
ã§æ°Žåè£çµŠããŠã¿ãŸãããïŒ
ã©ã®ã¢ã¯ã·ã§ã³ããã£ã¹ããããããŠãããããã¹ãããå Žåã«ã®ã¿ãã¢ãã¯ã¹ãã¢ãäœæããå¿
èŠããããŸãã
@carpeliam
ãã®ãªããžããªã®counter
ãštodomvc
äŸã®åäœãã¹ããåç
§ããŠãã ããã
å¥ã®ã³ã³ããŒãã³ãå ã§ã¹ããŒãã³ã³ããŒãã³ããã¬ã³ããªã³ã°ããŠããŠãæµ ãã¬ã³ãã©ãŒã䜿çšã§ããªããšããåé¡ããããŸããïŒã³ã³ããŒãã³ãã¯componentDid *ã¡ãœããã䜿çšããŠããŸããïŒã ç§ãã¡ãè¡ã£ãã®ã¯ãæ¥ç¶é¢æ°ãã¹ã¿ãïŒsinonã䜿çšïŒããŠãåçŽãªReactã³ã³ããŒãã³ããè¿ãé¢æ°ãè¿ãããšã§ããã
å£ãããããReact 0.14ã«ç§»è¡ã§ããããã«ãªã£ãããæµ ãã¬ã³ãã©ãŒã«ç§»è¡ããããšèããŠããŸããããã®æ¹æ³ã§ã¯ãåœé¢ã®éããã¹ãã®ãããã¯ã解é€ããŸããã
ç§ãããã«åé¡ããããŸãã ç§ã¯ReactãšReduxã®äž¡æ¹ã«ããªãæ £ããŠããªãã®ã§ããããç§ã劚ããŠããå¯èœæ§ããããŸãã ã©ã®ããã«ããŠãããåé¿ããããšãã§ããŸãããïŒ @songawee
ææ¡ãããäºéãšã¯ã¹ããŒãæ¹æ³ã§ã¯ãselectïŒmapStoreToStateïŒé¢æ°ã¯ãã¹ããããŸããã åå¥ã«ãã¹ãããã«ã¯ããšã¯ã¹ããŒãããå¿ èŠããããŸããããã¹ãã®ååãããã«å€æŽããŸãã
æµ ãã¬ã³ãã©ãŒãconnectã®ã©ãããããã³ã³ããŒãã³ãã§åäœãããæ¹æ³ãèŠã€ããããšã«èå³ããããŸãã ç§ã®çŸåšã®åé¡ã¯ãæµ ãã¬ã³ãã©ãŒã䜿çšãããšãConnectã³ã³ããŒãã³ãã®ã¿ãè¿ãããããšã§ããããã¯äºæ³ãããããšã§ãã
ææ¡ãããäºéãšã¯ã¹ããŒãæ¹æ³ã§ã¯ãselectïŒmapStoreToStateïŒé¢æ°ã¯ãã¹ããããŸããã åå¥ã«ãã¹ãããã«ã¯ããšã¯ã¹ããŒãããå¿ èŠããããŸããããã¹ãã®ååãããã«å€æŽããŸãã
å³å¯ã«ã¯ããã¹ãã®åã®äžã«ãã§ã¯ãããŸããã å®éãã¬ãã¥ãŒãµãŒãšäžç·ã«ã»ã¬ã¯ã¿ãŒé¢æ°ïŒæçµçã«ã¯mapStateToProps
ïŒãå®çŸ©ããããããäžç·ã«ãã¹ãããããšããå§ãããŸãã ãã®ããžãã¯ã¯UIããç¬ç«ããŠããããã³ãã«ããå¿
èŠã¯ãããŸããã ããã€ãã®ã»ã¬ã¯ã¿ãŒã®shopping-cart
äŸãèŠãŠãã ããã
@gaearon ããã®_selector_é¢æ°ã䜿çšããŠç¶æ ãããã¹ãŠã®ããŒã¿ãååŸããããšãææ¡ããŠããŸããïŒ ã»ãšãã©ã®å Žåã人ã ã¯ç¶æ ããããããã£ã®æãèªã¿åããããããã³ã³ããŒãã³ãã®å°éå ·ã«å²ãåœãŠãã ããªã®ã§ãããã¯å€ãã®äžèŠãªãªãŒããŒãããããããããŸãããïŒ
ã¯ããReduxã®äžè¬çãªæšå¥šãã¿ãŒã³ã¯ã state.some.nested.field
çŽæ¥ã¢ã¯ã»ã¹ããã®ã§ã¯ãªããã»ãšãã©ãã¹ãŠã®å Žæã§ã»ã¬ã¯ã¿ãŒé¢æ°ã䜿çšããããšã§ãã ãããã¯éåžžã«åçŽãªãåçŽãªãé¢æ°ã§ãããæãäžè¬çã«ã¯ãã¡ã¢åæ©èœãæäŸããReselectã©ã€ãã©ãªã䜿çšããŠãŸãšããããŸãã
ãªããããäœåãªãªãŒããŒããããçã¿åºãã®ã§ããããïŒ
ããã§èª¬æããããã«äºéãšã¯ã¹ããŒããå®è¡ããŠããŸããã connect
ãœãŒã¹ãèªã¿åããšããã³ã³ããããWrappedComponent
éçããããã£ã®ããã ãã³ã³ããŒãã³ããžã®åç
§ãä¿æããŠããããšãããããŸããã
ãããã£ãŠã代ããã«ïŒ
// 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
})
ããã ãããŒãžã§ã³ãå¿
èŠãªå Žæã§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ã¢ãžã¥ãŒã«ã䜿çšããå Žåãè£ é£ŸãããããããŒã³ã³ããŒãã³ããããã©ã«ããšããŠãšã¯ã¹ããŒãã§ãããã¬ãŒã³ãªReactã³ã³ããŒãã³ããè¿œå ã®ååä»ããšã¯ã¹ããŒãã«ããããšãã§ãããšæããŸãã äŸãã° ââ-
ãã®ãã¥ã¢ã«ãšã¯ã¹ããŒãã䜿çšãããšãã¡ã€ã³ã¢ããªã¯
import Header from './header.js'
以åãšåãããã«ã¹ããŒãã³ã³ããŒãã³ããæ¶è²»ã§ããŸããããã¹ãã§ã¯reduxããã€ãã¹ããŠãã³ã¢ã³ã³ããŒãã³ãã®åäœãimport {HeaderDumbComponent} from '../components/header.js'
çŽæ¥ãã¹ãã§ããŸãã