Share
Embed
Transcript
117 slides
-
Δ͜ͱ • React/ReduxͰTodoΞϓϦҎ্ͷͷΛ࡞Δ ͨΊʹඞཁͳཁૉͷβοͱͨ͠આ໌ • React, Redux, react-redux, react-router •
TodoΞϓϦΛ࡞ΕΔʢؾ͕͢ΔʣΑ͏ʹͳΔ ͷ͕ΰʔϧ -
Ξϯέʔτ̎ • ඇΤϯδχΞ • σβΠφʔ • ϑϩϯτΤϯυΤϯδχΞ • ϞόΠϧΞϓϦΤϯδχΞ •
ͦΕҎ֎ʢόοΫΤϯυͱ͔Πϯϑϥͱ͔ʣ -
ίϯςϯπ • शES2015+JSX • React • Redux • ඇಉظॲཧ, redux-thunk
• React-router • ςετ -
modules default import/export // MyApp.js import foo from './MyModule' //
MyModule.js export default function foo () { ... } -
modules named import/export // MyApp.js import { foo, Bar, baz
} from './MyModule' // MyModule.js export function foo () { ... } export class Bar { ... } export const baz = ... ࢀর: http://www.slideshare.net/teppeis/you-dont-know-es-modules -
class import MyModule, { myAdd, MyBaseClass } from './MyModule' class
MyClass extends MyBaseClass { constructor() { } add = (a, b) => { return myAdd(a, b) } show = ({ a, b }) => { const { foo, bar } = a const { baz: { box = 1 }} = b console.log(MyModule.show({ foo, bar, box })) } } ※babel plugin transform-class-properties͕ඞཁ -
destructuring assignment import MyModule, { myAdd, MyBaseClass } from './MyModule'
class MyClass extends MyBaseClass { constructor() { } add = (a, b) => { return myAdd(a, b) } show = ({ a, b }) => { const { foo, bar } = a const { baz: { box = 1 }} = b console.log(MyModule.show({ foo, bar, box })) } } show = (obj) => { const a = obj.a const b = obj.b const foo = a.foo const bar = a.bar const box = b.baz.box || 1 ... } 㱻 -
https://facebook.github.io/react/html-jsx.html JSX class NewComponent extends React.Component { render() { return
( <div> {/* Hello world */} <div className="awesome" style={{border: '1px solid red'}}> <label htmlFor="name">Enter your name: </label> <input type="text" id="name" /> </div> <p>Enter your HTML here</p> </div> ); } } <!-- Hello world --> <div class="awesome" style="border: 1px solid red"> <label for="name">Enter your name: </label> <input type="text" id="name" /> </div> <p>Enter your HTML here</p> -
JSX class NewComponent extends React.Component { render() { const todos
= ['eat', 'sleep'] return ( <div> <ul> { todos.map(item => <li>{item}</li>) } </ul> </div> ); } } <ul> <li>eat</li> <li>sleep</li> </ul> -
spread operator const list = [1, 2, 3] const list2
= [ ...list, 4] // [1, 2, 3, 4] const obj = { a: 'foo', b: 1 } const obj2 = { ...obj, b: 2, c: 'bar' } // { a: 'foo', b: 2, c: 'bar' } const prop = { className: 'my-class', style: { color: ‘white’ } } const div = ( <div {...prop} > <p>foo bar</p> <div> ) // <div class="my-class" style="color: white;”> // <p>foo bar</p> // </div> -
ηοτΞοϓ // Node.js 5.0Ҏ߱ΛΠϯετʔϧ cd your/working/directory git clone https://github.com/adwd/react-redux-hands-on npm
install npm run dev open http://localhost:3000ɹ// ͜Μͳը໘͕දࣔ͞ΕͨΒOK! -
αϯϓϧϓϩδΣΫτ • 3ͭͷTodoΞϓϦ • ReactͷΈ • React/Redux • React/Redux +
REST API 5PEPͷδϨϯϚ w 3FEVYͰෳࡶͳঢ়ଶΛཧͰ͖Δˠαϯϓϧ ʹͦΜͳෳࡶͳͷΛग़ͤͳ͍ˠ݁ہ5PEP -
αϯϓϧϓϩδΣΫτ • React, Redux, React-router, Babel, ESLint, Webpackͳ Ͳ͕͍͍ײ͡ʹઃఆ͞ΕͨϘΠϥʔϓϨʔτ͔Βfork •
https://github.com/davezuko/react-redux-starter-kit • มߋ: • react/reduxαϯϓϧίʔυͷՃɺmaterial-uiͷ ՃͳͲ • forkݩେنͳॻ͖͕͑ਐΈશવҧͬͯ·͢ -
αϯϓϧϓϩδΣΫτߏ • src • layouts - ΞϓϦέʔγϣϯશମͰදࣔͯ͠ ͍Δόʔɺυϩϫʔίϯϙʔωϯτ • redux
- reduxͷAction, Reducer • routes - react-routerͷϧʔςΟϯά • views - React/Reduxίϯϙʔωϯτ • server/api - json api -
React࠷খߏ // index.html <!DOCTYPE html> <html> <body> <div id="example"></div>
<script type=“text/javascript" src=“/foo/bar/sample.js"/> </body> </html> // sample.js import React from 'react' import ReactDOM from 'react-dom' import MyComponent from './MyComponent' class Example extends React.Component { render () { return ( <div> <h1>Hello, world!</h1> <MyComponent message='Hello, world!' /> </div> ) } } ReactDOM.render( <Example />, document.getElementById('example') ) -
React࠷খߏ // index.html <!DOCTYPE html> <html> <body> <div id="example"></div>
<script type=“text/javascript" src=“/foo/bar/sample.js"/> </body> </html> // sample.js import React from 'react' import ReactDOM from 'react-dom' import MyComponent from './MyComponent' class Example extends React.Component { render () { return ( <div> <h1>Hello, world!</h1> <MyComponent message='Hello, world!' /> </div> ) } } ReactDOM.render( <Example />, document.getElementById('example') ) React.Component -
React ίϯϙʔωϯτ import React from 'react' class Sample extends
React.Component { constructor (props) { super(props) this.state = { text: props.prefix + 'learn react' } } render () { return ( <p>{this.state.text}</p> ) } } -
React ίϯϙʔωϯτ import React from 'react' class Sample extends
React.Component { constructor (props) { super(props) this.state = { text: props.prefix + 'learn react' } } render () { return ( <p>{this.state.text}</p> ) } } React.ComponentΛܧঝ͠ɺ renderϝιουΛ࣋ͭΫϥε -
React ίϯϙʔωϯτ import React from 'react' class Sample extends
React.Component { constructor (props) { super(props) this.state = { text: props.prefix + 'learn react' } } render () { return ( <p>{this.state.text}</p> ) } } ֎෦͔ΒpropsΛड͚औΔ ෦ʹstateΛ࣋ͭ -
import React from 'react' class MyReactComponent extends React.Component { render
() { return ( <div> <h1>This is My Component</h1> <div> {this.props.children} </div> </div> ) } } <MyReactComponent> <p>learn react</p> <p>learn flux</p> </MyReactComponent> props.children -
import React from 'react' class MyReactComponent extends React.Component { render
() { return ( <div> <h1>This is My Component</h1> <div> {this.props.children} </div> </div> ) } } <MyReactComponent> <p>learn react</p> <p>learn flux</p> </MyReactComponent> props.children Reactίϯϙʔωϯτʹωετͨ͠ ཁૉprops.childrenʹೖΔ -
Components • TodoList • TodoΛstateͱͯ͠ཧ • TodoItem • TodoΛҰͭදࣔ •
TodoআϘλϯΛදࣔ 5PEP-JTU 5PEP*UFN ˣ5PEP-JTUίϯϙʔωϯτͷ TUBUFΛͦͷ··දࣔ -
TodoList state { newTodo: '', todos: [ 'learn react’,
'learn flux’, 'learn redux’ ] } • DOMͷૢ࡞͠ͳ͍ɺ ঢ়ଶΛૢ࡞ͯ͠React͕ͦΕ Λඳը͢Δ -
TodoList render() render () { return ( <div> <h1>React
Sample</h1> <h2>Todos</h2> <input type='text' value={this.state.newTodo} onChange={this.changeText} /> <button onClick={this.addTodo}>add todo</button> { this.state.todos.map((text, index) => ( <TodoItem index={index} onRemove={this.deleteTodoItem} key={index}> {text} </TodoItem> )) } … </div> ) } -
TodoList render() render () { return ( <div> <h1>React
Sample</h1> <h2>Todos</h2> <input type='text' value={this.state.newTodo} onChange={this.changeText} /> <button onClick={this.addTodo}>add todo</button> { this.state.todos.map((text, index) => ( <TodoItem index={index} onRemove={this.deleteTodoItem} key={index}> {text} </TodoItem> )) } … </div> ) } -
import React, { Component } from 'react' export default class
TodoList extends Component { constructor (props) { super(props) this.state = { newTodo: '', todos: [ 'learn react’, 'learn flux’, 'learn redux' ] } } ... } TodoList ίϯετϥΫλ -
import React, { Component } from 'react' export default class
TodoList extends Component { constructor (props) { super(props) this.state = { newTodo: '', todos: [ 'learn react’, 'learn flux’, 'learn redux' ] } } ... } TodoList ίϯετϥΫλ ঢ়ଶͷॳظԽ -
TodoList ΫϥεϓϩύςΟ changeText = (e) => { this.setState({ newTodo:
e.target.value }) } addTodo = () => { const newTodo = this.state.newTodo this.setState({ newTodo: '', todos: [...this.state.todos, newTodo] }) } deleteTodoItem = (index) => { this.setState({ todos: this.state.todos.filter((todo, i) => i !== index) }) } -
changeText = (e) => { this.setState({ newTodo: e.target.value }) }
addTodo = () => { const newTodo = this.state.newTodo this.setState({ newTodo: '', todos: [...this.state.todos, newTodo] }) } deleteTodoItem = (index) => { this.setState({ todos: this.state.todos.filter((todo, i) => i !== index) }) } TodoList ΫϥεϓϩύςΟ this.setState stateͷࢦఆͨ͠ύε ͚ͩߋ৽͢Δ -
TodoItem.js import React, { Component, PropTypes } from 'react'
export default class TodoItem extends Component { static propTypes = { index: PropTypes.number, onRemove: PropTypes.func, children: PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.node), React.PropTypes.node ]) } render() { const { index, onRemove, children } = this.props; return ( <div> {`${index + 1}`}: {children} <input type='button' value='x' onClick={() => onRemove(index)} /> </div> ); } } -
TodoItem.js import React, { Component, PropTypes } from 'react'
export default class TodoItem extends Component { static propTypes = { index: PropTypes.number, onRemove: PropTypes.func, children: PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.node), React.PropTypes.node ]) } render() { const { index, onRemove, children } = this.props; return ( <div> {`${index + 1}`}: {children} <input type='button' value='x' onClick={() => onRemove(index)} /> </div> ); } } render() -
TodoItem.js import React, { Component, PropTypes } from 'react'
export default class TodoItem extends Component { static propTypes = { index: PropTypes.number, onRemove: PropTypes.func, children: PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.node), React.PropTypes.node ]) } render() { const { index, onRemove, children } = this.props; return ( <div> {`${index + 1}`}: {children} <input type='button' value='x' onClick={() => onRemove(index)} /> </div> ); } } propTypes: ࣮ߦ࣌ܕνΣοΫ -
ίʔυΛ͍ͬͯ͡ΈΔ • jsϑΝΠϧΛมߋɾอଘ͢ΔͱࣗಈͰө͞ΕΔͣ • Webpack Hot Reloading • localhost:3000/react •
src/views/ReactSample/TodoList.js • src/views/ReactSample/TodoItem.js -
React ·ͱΊ • ViewϥΠϒϥϦ • ίϯϙʔωϯτ • props, props.children •
this.state, this.setState -
Redux • FluxΞʔΩςΫνϟͷ࣮ͷҰͭ • Flux࣮facebook/flux͔reduxͷͲͪΒ͔ͱ͍͏ งғؾʁ • υΩϡϝϯτ͕ॆ࣮ http://redux.js.org •
࣮͕300ߦ͔͠ͳ͍ͷͰಡΉͷΞϦ • ΞϓϦέʔγϣϯͷঢ়ଶΛҰͭͷJSONͰදݱ͢Δ -
Action • ΞϓϦέʔγϣϯͷঢ়ଶΛมԽͤ͞ΔΑ͏ ͳಈ࡞Λදݱͨ͠plain javascript object • ex) TodoͷՃɺAPIΞΫηεͷཁٻ/ޭ •
typeϑΟʔϧυ͕ඞཁ { "type": "ADD_TODO", "text": "Build my first Redux app" } { "type": "FETCH_USER_SUCCEEDED", "id": 1, "name": "Dan Abramov", } -
Reducer • ࣮ߦ͞Εͨactionͱݱࡏͷstate͔Βɺ࣍ͷ stateΛܭࢉ͢Δ • ͨͩͷؔ • (currentState, action) =>
nextState • action + reducer === setState తͳʁ -
Reducer const initalState = [] function todo(state = initalState,
action) { switch (action.type) { case "ADD_TODO": return [...state, action.text] case "DELETE_TODO": return state.filter( (text, index) => index !== action.index ) default: return state } } state: [], action: { type: “ADD_TODO”, text: “hi” } ! nextState = reducer(state, action) ! nextState: [ “hi” ] -
Store API • createStore: (reducer) => Store • store.dispatch: ΞΫγϣϯͷ࣮ߦ
import { createStore } from 'redux' import todo from './TodoReducer' const store = createStore(todo) store.dispatch({type: 'ADD_TODO', text: 'learn react'}) store.subscribe(() => { console.log(store.getState()) // [ ‘learn react’ ] }) -
View • ͳΜͰΑ͍ • console.log, vue.js, Angular 1/2 … •
Reactͷ߹ɺReactͱReduxͷ࿈ܞʹ react-reduxΛ͏ -
react-redux class TodoList extends React.Component { render() { <div> <button
onClick={this.props.dispatch({ type: 'ADD_TODO', text: ‘learn react' })} /> { this.props.todo.map((text, index) => { <TodoItem key={index}> {text} </TodoItem> }) } </div> } } store.dispatch store.getState().todo -
react-redux: connect import { connect } from 'react-redux' class
TodoList extends React.Component { render() { <div> <button onClick={this.props.dispatch({type: 'ADD_TODO', text: 'learn redux'})} /> { this.props.todo.map((text, index) => { <TodoItem key={index}>{text}</TodoItem> }) } </div> } } const connectedTodoList = connect( state => { todo: state.todo } )(TodoList) export default connectedTodoList { "todo": "...", "users": "...", "foo": "..." } -
react-redux: connect import { connect } from 'react-redux' class
TodoList extends React.Component { render() { <div> <button onClick={this.props.dispatch({type: 'ADD_TODO', text: 'learn redux'})} /> { this.props.todo.map((text, index) => { <TodoItem key={index}>{text}</TodoItem> }) } </div> } } const connectedTodoList = connect( state => { todo: state.todo } )(TodoList) export default connectedTodoList store.dispatch store.getState().todo ঢ়ଶΛද͢Jsonશମ͔Βtodo ෦͚ͩΛTodoListʹInject -
react-redux: Provider import React from 'react' import { render
} from 'react-dom' import App from './components/App' render( <App />, document.getElementById('root') ) import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' import { createStore } from 'redux' import todo from './reducers' import App from './components/App' const store = createStore(todo) render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') ) -
Action (ActionCreator) // src/redux/modules/todo.js export function addTodo () {
return { type: 'ADD_TODO' } } export function removeTodo (index) { return { type: 'REMOVE_TODO', index } } export function editNewTodo (text) { return { type: 'EDIT_NEW_TODO', text } } • ͦ͜Β͡Ύ͏ʹ dispatch({ type: ‘ADD_TODO’ })Λॻ͔ͳ ͍Α͏ʹؔʹ͓ͯ͘͠ • dispatch(addTodo()) -
// src/redux/modules/todo.js // Initial state const initialState = { newTodo:
'', todos: [ 'learn react', 'learn flux', 'learn redux' ] } // Reducer export default function todoReducer (state = initialState, action) { switch (action.type) { case 'ADD_TODO': return { newTodo: '', todos: [...state.todos, state.newTodo] } case 'REMOVE_TODO': return { ...state, todos: state.todos.filter((todo, i) => i !== action.index) } case 'EDIT_NEW_TODO': return { ...state, newTodo: action.text } default: return state } } Reducer -
Component // src/views/ReduxSample/ReduxSample.js import { addTodo, removeTodo, editNewTodo }
from ‘../../redux/modules/todo’ class ReduxSample extends Component { render () { const { todo: { newTodo, todos }, dispatch } = this.props return ( <div> <input type='text' value={newTodo} onChange={(e) => dispatch(editNewTodo(e.target.value)} /> ... </div> ) } } const ConnectedReduxSample = connect( state => ({todo: state.todo}) )(ReduxSample) export default ConnectedReduxSample -
Redux, react-redux ·ͱΊ • Redux • Flux, ୯ํσʔλϑϩʔ • Action,
Reducer, Store, View • react-redux • connectͰReduxͷঢ়ଶΛpropsʹinject • ProviderͰϧʔτͷίϯϙʔωϯτΛϥοϓ • ࣭ͳͲʁ -
ඇಉظαϯϓϧ server: localhost:3000/api/ • GET todos • POST todos/add
• DELETE todos/:index source: server/api/todos.js 2 seconds delay -
ඇಉظॲཧ • ReduxͷActionʹඇಉظॲཧΛͷ͍ͤͨ • redux-thunkΛ͏ • redux-thunk: redux middlewareͷͻͱͭͰɺ promiseΛฦؔ͢ΛActionʹͤΔ
• redux middleware: reducer࣮ߦ࣌ʹ͍͍ײ ͡ʹͯ͘͠ΕΔͭ -
Async Action • APIϦεΤετૹ৴ɾϨεϙϯεड৴ͷ2 ΞΫγϣϯΛ࣮ߦ͢Δ 2 seconds after request
action response action -
Async Action ඇಉظΞΫγϣϯ = dispatch => promiseͳؔ // action
creator const request = () => { type: 'REQUESTING' } const receive = (data) => { type: 'RECEIVED', data } // async action creator export function apiAccess() { return dispatch => { dispatch(request()) axios.get('api/sample') .then(response => { dispatch(receive(response.data)) }) .catch(e => { // error }) } } -
// action creator const request = () => { type:
'REQUESTING' } const receive = (data) => { type: 'RECEIVED', data } // async action creator export function apiAccess() { return dispatch => { dispatch(request()) axios.get('api/sample') .then(response => { dispatch(receive(response.data)) }) .catch(e => { // error }) } } Async Action ϦΫΤετૹ৴લʹ requestΞΫγϣϯ ϨεϙϯεΛड৴ͨ͠ͱ͜ΖͰ receiveΞΫγϣϯ -
Async Action (Reducer) const initialState = { loading: false,
todos = [] } function asyncReducer(state = initialState, action) { switch (action.type) { case 'REQUESTING': return { loading: true, todos = [] } case 'RECEIVED': return { loading: false, todos = action.data } default: return state } } -
Async Action (Component) class AsyncComponent extends React.Component { componentDidMount()
{ this.props.dispatch(fetchTodos()) } render () { <div> { this.props.todo.loading ? <p>loading...</p> : <TodoList {...this.props.todo.todos} /> } </div> } } export default connect(state => { todo: state.todo })(AsyncComponent) -
Async Action (Component) class AsyncComponent extends React.Component { componentDidMount()
{ this.props.dispatch(fetchTodos()) } render () { <div> { this.props.todo.loading ? <p>loading...</p> : <TodoList {...this.props.todo.todos} /> } </div> } } export default connect(state => { todo: state.todo })(AsyncComponent) loadingͰදࣔΛΓସ͑ componentDidMountͰ APIʹϦΫΤετΛૹ৴ -
ඇಉظॲཧ·ͱΊ • redux-thunk • ࠷ۙredux-saga͕ਓؾΒ͍͠(?) • Netflixͷredux-observableͱ͔ • ඇಉظɾ෭࡞༻Λѻ͏Redux middlewareͷઓࠃ࣌ײ
• ΞΫηε։࢝ɾྃͰ2ճΞΫγϣϯΛ࣮ߦ • Storeͷঢ়ଶʹAPIΞΫηεঢ়ଶΛஔ͍͓ͯ͘ • ࣭ͳͲʁ -
ओͳAPI • <Router>, <Route> • ϧʔςΟϯάΛߦ͏ • <Route path=“/users”
component={Users} /> • <Link> • <a href=“…” />ͷׂ • <Link to=“/users”>show users</Link> • withRouter • ReactίϯϙʔωϯτͷpropsʹrouterػೳΛInject • withRouter(MyComponent) -
routing // src/routes/index.js import React from 'react' import {
Route, IndexRoute } from 'react-router' import CoreLayout from 'layouts/CoreLayout/CoreLayout' import HomeView from 'views/HomeView/HomeView' import ReactSample from 'views/ReactSample' import ReduxSample from 'views/ReduxSample' import ReduxAsyncSample from 'views/ReduxAsyncSample' export default (store) => ( <Route path='/' component={CoreLayout}> <IndexRoute component={HomeView} /> <Route path='react' component={ReactSample} /> <Route path='redux' component={ReduxSample} /> <Route path='redux-async' component={ReduxAsyncSample} /> </Route> ) -
routing // src/routes/index.js import React from 'react' import {
Route, IndexRoute } from 'react-router' import CoreLayout from 'layouts/CoreLayout/CoreLayout' import HomeView from 'views/HomeView/HomeView' import ReactSample from 'views/ReactSample' import ReduxSample from 'views/ReduxSample' import ReduxAsyncSample from 'views/ReduxAsyncSample' export default (store) => ( <Route path='/' component={CoreLayout}> <IndexRoute component={HomeView} /> <Route path='react' component={ReactSample} /> <Route path='redux' component={ReduxSample} /> <Route path='redux-async' component={ReduxAsyncSample} /> </Route> ) pathͰύεΛࢦఆ IndexRoute path=‘users/:id/edit’ ͳͲՄೳ -
Component // src/views/HomeView/HomeView.js import React, { Component, PropTypes }
from 'react' import { connect } from 'react-redux' import { Link, withRouter } from 'react-router' export class HomeView extends Component { render () { const toReduxAsync = () => this.props.router.push('redux-async') return ( ... <Link to='react'><p>react sample</p></Link> <Link to='redux'><p>redux sample</p></Link> <button onClick={toReduxAsync}>redux-async sample</button> ... ) } } HomeView.propTypes = { router: PropTypes.shape({push: PropTypes.func}) } export default connect(state => state)(withRouter(HomeView)) -
Component // src/views/HomeView/HomeView.js import React, { Component, PropTypes }
from 'react' import { connect } from 'react-redux' import { Link, withRouter } from 'react-router' export class HomeView extends Component { render () { const toReduxAsync = () => this.props.router.push('redux-async') return ( ... <Link to='react'><p>react sample</p></Link> <Link to='redux'><p>redux sample</p></Link> <button onClick={toReduxAsync}>redux-async sample</button> ... ) } } HomeView.propTypes = { router: PropTypes.shape({push: PropTypes.func}) } export default connect(state => state)(withRouter(HomeView)) <a />ͷ͔ΘΓʹ<Link /> -
Component // src/views/HomeView/HomeView.js import React, { Component, PropTypes }
from 'react' import { connect } from 'react-redux' import { Link, withRouter } from 'react-router' export class HomeView extends Component { render () { const toReduxAsync = () => this.props.router.push('redux-async') return ( ... <Link to='react'><p>react sample</p></Link> <Link to='redux'><p>redux sample</p></Link> <button onClick={toReduxAsync}>redux-async sample</button> ... ) } } HomeView.propTypes = { router: PropTypes.shape({push: PropTypes.func}) } export default connect(state => state)(withRouter(HomeView)) router.push withRouterͰίϯϙʔωϯτͷ propsʹrouterΛInject -
αϯϓϧ // tests/views/ReactSample.spec.js import React from 'react' import {
shallow } from 'enzyme' import assert from 'power-assert' import ReactSample from 'views/ReactSample' import TodoItem from 'views/ReactSample/TodoItem' describe('ReactSample', function () { it('Should include an <h1> with \'React Sample\' text.', () => { const wrapper = shallow(<ReactSample />) assert(wrapper.childAt(0).type() === 'h1') assert(wrapper.childAt(0).text() === 'React Sample') }) it('should render three items', () => { const wrapper = shallow(<ReactSample />) assert(wrapper.find(TodoItem).length === 3) }) }) -
shallow rendering <div> <h1>React Sample</h1> <h2>Todos</h2> <input type='text' />
<button>add todo</button> <TodoItem>{'learn react'}</TodoItem> <TodoItem>{'learn flux'}</TodoItem> <TodoItem>{'learn redux'}</TodoItem> <h3>state: </h3> <pre> ... </pre> </div> shallow(<TodoList />) -
shallow rendering it('passes text to TodoItem', () => {
const wrapper = shallow(<ReactSample />) assert.deepEqual(wrapper.state('todos'), ['learn react', 'learn flux', 'learn redux']) assert(wrapper.find(TodoItem).length === 3) assert(wrapper.find(TodoItem).at(0).props().children === 'learn react') assert(wrapper.find(TodoItem).at(1).props().children === 'learn flux') assert(wrapper.find(TodoItem).at(2).props().children === 'learn redux') }) -
Full rendering (mount) it('should render todo texts', () =>
{ const wrapper = mount(<ReactSample />) assert(wrapper.find('div').at(1).text() === '1: learn react ') assert(wrapper.find('div').at(2).text() === '2: learn flux ') assert(wrapper.find('div').at(3).text() === '3: learn redux ') }) -
Full rendering (mount) import React from 'react' import {
mount } from 'enzyme' import { spy } from 'sinon' import assert from 'power-assert' import { ReduxAsyncSample } from 'views/ReduxAsyncSample/ReduxAsyncSample' describe('ReduxAsyncSample', () => { it('Should call fetch when componentDidMount', () => { spy(ReduxAsyncSample.prototype, 'componentDidMount'); const onFetch = spy() const wrapper = mount( <ReduxAsyncSample {...defaultProp} fetch={onFetch}/> ) assert(ReduxAsyncSample.prototype.componentDidMount.calledOnce) assert(onFetch.calledOnce) }) }) ˞XSJUF@NPVOU@UFTUϒϥϯνࢀর -
·ͱΊ • React: ViewϥΠϒϥϦ • Redux: ΞϓϦέʔγϣϯͷঢ়ଶཧ • React-Redux: ReactͱReduxͷ࿈ܞ
• Redux-Thunk: ඇಉظॲཧ • React-Router: URLϧʔςΟϯά • enzyme: ReactίϯϙʔωϯτςετϢʔςΟϦςΟ -
reference • http://qiita.com/advent-calendar/2014/reactjs • ҰਓReact.js Advent Calendar 2014 • एׯݹ͍͚Ͳೖʹ
• https://speakerdeck.com/koba04/the-state-of-react-dot-js-2016 • The state of React.js 2016 • React࠷৽ͷಈɺҰਓAdvent Calendar͔ΒͷΩϟονΞοϓ • https://facebook.github.io/react/docs • http://redux.js.org/ • ެࣜυΩϡϝϯτ͕ॆ࣮ • http://postd.cc/getting-started-with-tdd-in-react/ • ReactͰTDDʢςετۦಈ։ൃʣΛ࢝ΊΑ͏ : ڥߏங͔Βςετ࡞ɺػ ೳ࣮·ͰͷৄղΨΠυ • ຊʹθϩ͔ΒTDDͰTodoΞϓϦΈ͍ͨͳͷΛ࡞ΕΔ -
reference • Web+DB Press vol. 87 • @teppeis͞ΜͷES2015ಛू • http://www.slideshare.net/teppeis/effective-es6
• @teppeis͞ΜʹࡢฐࣾͷࣾษڧձͰൃද͍͍ͯͨͩͨ࣌͠ͷεϥΠυ • http://qiita.com/kuy • Redux, redux-sagaͷهࣄΛࢁॻ͍͍ͯΔ@kuy͞Μ • ൃԻʮΧΠʯ͞Μ -
reference • http://redux.js.org/docs/basics/UsageWithReact.html • https://medium.com/@dan_abramov/smart-and-dumb- components-7ca2f9a7c7d0 • ίϯϙʔωϯτΛContainer/ViewͰ͚Δ • https://facebook.github.io/flux/docs/flux-utils.html#best-practices
• facebook͕ڍ͛ͨfluxΞʔΩςΫνϟͰͷϕετϓϥΫςΟε • https://www.gitbook.com/book/tonyhb/redux-without-profanity/details • DockerࣾͰReduxΛͬͨϊϋΛॻ͍ͨgitbook • StoreͱαʔόʔͷσʔλΛಉظͤ͞Δ • https://speakerdeck.com/chrisui/real-world-redux • Immutable.jsΛͬͨStore, localStorageͰͷӬଓԽͳͲ • https://speakerdeck.com/joere/flowtype-with-flow • FlowtypeͰ੩తܕνΣοΫ ͭ͘ΕΔˠΩϨΠʹεέʔϧ͢Δͭ͘Γʹ͢Δ