Save 37% off PRO during our Black Friday Sale! »

React/Redux Introduction

949b89ab31e756ae9681eb535265391e?s=47 adwd
July 08, 2016

React/Redux Introduction

949b89ab31e756ae9681eb535265391e?s=128

adwd

July 08, 2016
Tweet

Transcript

  1. React/Redux Introduction 2016/7/8

  2. ࣗݾ঺հ ੢ాխത 2015/05ʹBizreachʹస৬ɺWebܦݧ1೥ ٻਓݕࡧΤϯδϯελϯόΠͷࣾ಺޲͚؅ཧ ը໘ΛReact/ReduxͰ։ൃத https://github.com/adwd https://twitter.com/adwd118 

  3. None
  4. None
  5. ελϯόΠ؅ཧը໘೥ද 2015/5 ~  2015/5 jQuery 2015/7 AngularJS 1 2015/12

    React/Redux
  6. ΍Βͳ͍͜ͱ • React/Reduxͷৄ͍͠આ໌ • React vs Angular, facebook/flux vs redux

    .. 
  7. ΍Δ͜ͱ • React/ReduxͰTodoΞϓϦҎ্ͷ΋ͷΛ࡞Δ ͨΊʹඞཁͳཁૉͷβοͱͨ͠આ໌ • React, Redux, react-redux, react-router •

    TodoΞϓϦΛ࡞ΕΔʢؾ͕͢ΔʣΑ͏ʹͳΔ ͷ͕ΰʔϧ 
  8. Ξϯέʔτ • ϓϩάϥϛϯάɾjavascriptະܦݧ • ϓϩάϥϛϯάɾjavascriptνϣοτϫΧϧʢॳ৺ऀʣ • React/ReduxͷνϡʔτϦΞϧΛ΍ͬͨʢ΍Ζ͏ͱͨ͠ʣ • React/ReduxνϣοτσΩϧʢීஈ͔Β࢖ͬͯΔʣ 

  9. Ξϯέʔτ̎ • ඇΤϯδχΞ • σβΠφʔ • ϑϩϯτΤϯυΤϯδχΞ • ϞόΠϧΞϓϦΤϯδχΞ •

    ͦΕҎ֎ʢόοΫΤϯυͱ͔Πϯϑϥͱ͔ʣ 
  10. ίϯςϯπ • ଎शES2015+JSX • React • Redux • ඇಉظॲཧ, redux-thunk

    • React-router • ςετ 
  11. modules default import/export // MyApp.js import foo from './MyModule' //

    MyModule.js export default function foo () { ... }
  12. 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
  13. 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͕ඞཁ
  14. 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 ... } 㱻
  15. 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>
  16. 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>
  17. 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>
  18. αϯϓϧϓϩδΣΫτ

  19. ηοτΞοϓ // 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!
  20. αϯϓϧϓϩδΣΫτ • 3ͭͷTodoΞϓϦ • ReactͷΈ • React/Redux • React/Redux +

    REST API  5PEPͷδϨϯϚ w 3FEVYͰෳࡶͳঢ়ଶΛ؅ཧͰ͖Δˠαϯϓϧ ʹͦΜͳෳࡶͳͷΛग़ͤͳ͍ˠ݁ہ5PEP
  21. αϯϓϧϓϩδΣΫτ • React, Redux, React-router, Babel, ESLint, Webpackͳ Ͳ͕͍͍ײ͡ʹઃఆ͞ΕͨϘΠϥʔϓϨʔτ͔Βfork •

    https://github.com/davezuko/react-redux-starter-kit • มߋ఺: • react/reduxαϯϓϧίʔυͷ௥Ճɺmaterial-uiͷ௥ ՃͳͲ • forkݩ͸େن໛ͳॻ͖׵͕͑ਐΈશવҧͬͯ·͢ 
  22. αϯϓϧϓϩδΣΫτߏ੒ • src • layouts - ΞϓϦέʔγϣϯશମͰදࣔͯ͠ ͍Δόʔɺυϩϫʔίϯϙʔωϯτ • redux

    - reduxͷAction, Reducer • routes - react-routerͷϧʔςΟϯά • views - React/Reduxίϯϙʔωϯτ • server/api - json api 
  23. Redux DevTools • Reduxͷ։ൃʹศརͳChrome֦ு • https://chrome.google.com/webstore/ detail/redux-devtools/ lmhkpmbekcpmknklioeibfkpmmfibljd 

  24. ͜͜Ͱը໘Λ͞ΘͬͯΈΔ

  25. ESLintͷεεϝ • javascriptͷจ๏ΛνΣοΫͯ͘͠ΕΔ • ΤσΟλͱ࿈ܞ͢Δͷ͕Φεεϝ • eslint —fix Ͱ͋Δఔ౓ͷमਖ਼͸΍ͬͯ͘ΕΔ •

    νʔϜ։ൃͰίʔυϨϏϡʔָ͕ʹͳΔ 
  26. ESLintͷεεϝ  • ྫɿ Atom + linter-eslint

  27. React

  28. React  • ViewϥΠϒϥϦ • js-centric: શ෦jsͷதʹॻ͘ • ίϯϙʔωϯτࢦ޲

  29. 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') )
  30. 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
  31. 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> ) } }
  32. 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ϝιουΛ࣋ͭΫϥε
  33. 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Λ࣋ͭ
  34. 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 
  35. 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ʹೖΔ
  36. React sample • localhost:3000/react • src/views/ReactSample/TodoList.js • src/views/ReactSample/TodoItem.js 

  37. αϯϓϧΛ͞ΘͬͯΈΔ

  38. Components • TodoList • TodoΛstateͱͯ͠؅ཧ • TodoItem • TodoΛҰͭදࣔ •

    Todo࡟আϘλϯΛදࣔ  5PEP-JTU 5PEP*UFN ˣ5PEP-JTUίϯϙʔωϯτͷ TUBUFΛͦͷ··දࣔ
  39. TodoList state  { newTodo: '', todos: [ 'learn react’,

    'learn flux’, 'learn redux’ ] } • ௚઀DOMͷૢ࡞͸͠ͳ͍ɺ ঢ়ଶΛૢ࡞ͯ͠React͕ͦΕ Λඳը͢Δ
  40. 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> ) }
  41. 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> ) }
  42. 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 ίϯετϥΫλ 
  43. 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 ίϯετϥΫλ  ঢ়ଶͷॳظԽ
  44. 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) }) }
  45. 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ͷࢦఆͨ͠ύε ͚ͩߋ৽͢Δ
  46. 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> ); } }
  47. 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()
  48. 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: ࣮ߦ࣌ܕνΣοΫ
  49. ίʔυΛ͍ͬͯ͡ΈΔ • jsϑΝΠϧΛมߋɾอଘ͢ΔͱࣗಈͰ൓ө͞ΕΔ͸ͣ • Webpack Hot Reloading • localhost:3000/react •

    src/views/ReactSample/TodoList.js • src/views/ReactSample/TodoItem.js 
  50. ࣮ࡍʹ͍ͬͯ͡ΈΔ

  51. React ·ͱΊ • ViewϥΠϒϥϦ • ίϯϙʔωϯτ • props, props.children •

    this.state, this.setState 
  52. React ·ͱΊ • ViewϥΠϒϥϦ • ͳͷͰMVCͰ͍͏Model, Controllerʹ͋ͨΔػೳ͕ͳ͍ • this.state, this.setStateͰෳࡶͳঢ়ଶΛ؅ཧ͢Δͷ͸…

    → Redux • ࣭໰ͱ͔ʁ 
  53. Redux

  54. Redux • FluxΞʔΩςΫνϟͷ࣮૷ͷҰͭ • Flux࣮૷͸facebook/flux͔reduxͷͲͪΒ͔ͱ͍͏ งғؾʁ • υΩϡϝϯτ͕ॆ࣮ http://redux.js.org •

    ࣮૷͕300ߦ͔͠ͳ͍ͷͰಡΉͷ΋ΞϦ • ΞϓϦέʔγϣϯͷঢ়ଶΛҰͭͷJSONͰදݱ͢Δ 
  55. ୯ํ޲σʔλϑϩʔ  IUUQXXXTMJEFTIBSFOFU+POBT0IMTTPOVTJOHSFEVY

  56. ୯ํ޲σʔλϑϩʔ • View͸Storeͷঢ়ଶΛඳը͢Δ͚ͩ • ঢ়ଶΛมߋ͢Δͷ͸Action͚ͩ • Reducer͸(currentState, action) => nextStateͳ

    ͨͩͷؔ਺ 
  57. 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", }
  58. Reducer • ࣮ߦ͞Εͨactionͱݱࡏͷstate͔Βɺ࣍ͷ stateΛܭࢉ͢Δ • ͨͩͷؔ਺ • (currentState, action) =>

    nextState • action + reducer === setState తͳʁ 
  59. 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” ]
  60. Store • ΞϓϦέʔγϣϯͷঢ়ଶΛอ࣋͢Δ • Store͸ΞϓϦέʔγϣϯதʹͻͱͭ • ΞϓϦέʔγϣϯͷঢ়ଶ͸1ຕͷJsonʹͳΔ 

  61. 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’ ] })
  62. View • ͳΜͰ΋Α͍ • console.log, vue.js, Angular 1/2 … •

    Reactͷ৔߹͸ɺReactͱReduxͷ࿈ܞʹ react-reduxΛ࢖͏ 
  63. react-redux

  64. react-redux • ReactͱReduxΛͭͳ͙ • ReduxͷStoreͱReactͷprops • ReduxͷΞΫγϣϯͱReactͷΠϕϯτ 

  65. react-redux • ReactͱReduxΛͭͳ͙ • ReactίϯϙʔωϯτͷpropsʹɺRedux StoreͷstateͱdispatchΛInject͢Δ 

  66. 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
  67. react-redux • API͸2͚ͭͩ • connect • Provider 

  68. react-redux: connect  • Redux Storeͷstate͔Βඞཁͳ෦෼ΛReact ίϯϙʔωϯτͷpropsʹ஫ೖ͢ΔηϨΫλ

  69. 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": "..." }
  70. 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
  71. react-redux: Provider  • connectΛݺ΂ΔΑ͏ʹ͢Δ • ReactΞϓϦέʔγϣϯͷϧʔτίϯϙʔ ωϯτΛProviderͰϥοϓ

  72. 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') )
  73. Reduxαϯϓϧ • http://localhost:3000/redux • src/views/ReduxSample/ReduxSample.js • src/redux/modules/todo.js 

  74. Redux DevTools • ActionͱStateΛ࣌ܥྻͰදࣔ • աڈͷStateʹߦͬͨΓͰ͖Δ 

  75. αϯϓϧΛ͞ΘͬͯΈΔ

  76. 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())
  77. // 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 
  78. 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
  79. Redux, react-redux ·ͱΊ • Redux • Flux, ୯ํ޲σʔλϑϩʔ • Action,

    Reducer, Store, View • react-redux • connectͰReduxͷঢ়ଶΛpropsʹinject • ProviderͰϧʔτͷίϯϙʔωϯτΛϥοϓ • ࣭໰ͳͲʁ 
  80. ͜͜·Ͱͷ·ͱΊ • React • Reduxɺreact-redux • TodoΞϓϦΛ࡞Δ͜ͱ͕Ͱ͖ͦ͏ 

  81. ͔͜͜Β • ඇಉظॲཧ • Web APIΛୟ͘Α͏ͳॲཧ • ϧʔςΟϯά • /users/1

    • ςετ 
  82. ඇಉظॲཧ

  83. ඇಉظαϯϓϧ • http://localhost:3000/redux-async • src/views/ReduxAsyncSample/ • src/modules/asyncTodo/asyncTodo.js 

  84. ඇಉظαϯϓϧ  server: localhost:3000/api/ • GET todos • POST todos/add

    • DELETE todos/:index source: server/api/todos.js 2 seconds delay
  85. αϯϓϧΛ͞ΘͬͯΈΔ

  86. ඇಉظॲཧ • ReduxͷActionʹඇಉظॲཧΛͷ͍ͤͨ • redux-thunkΛ࢖͏ • redux-thunk: redux middlewareͷͻͱͭͰɺ promiseΛฦؔ͢਺ΛActionʹ౉ͤΔ

    • redux middleware: reducer࣮ߦ࣌ʹ͍͍ײ ͡ʹͯ͘͠ΕΔ΍ͭ 
  87. Async Action  • API΁ϦεΤετૹ৴ɾϨεϙϯεड৴ͷ2 ౓ΞΫγϣϯΛ࣮ߦ͢Δ 2 seconds after request

    action response action
  88. 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 }) } }
  89. // 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ΞΫγϣϯ
  90. 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 } }
  91. 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)
  92. 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ʹϦΫΤετΛૹ৴
  93. ඇಉظॲཧ·ͱΊ • redux-thunk • ࠷ۙ͸redux-saga͕ਓؾΒ͍͠(?) • Netflix੡ͷredux-observableͱ͔΋ • ඇಉظɾ෭࡞༻Λѻ͏Redux middlewareͷઓࠃ࣌୅ײ

    • ΞΫηε։࢝ɾ׬ྃͰ2ճΞΫγϣϯΛ࣮ߦ • Storeͷঢ়ଶʹAPIΞΫηεঢ়ଶΛஔ͍͓ͯ͘ • ࣭໰ͳͲʁ 
  94. react-router

  95. react-router • URLભҠͱReactίϯϙʔωϯτදࣔΛಉظ ͢Δ • src/routes/index.js 

  96. ओͳAPI  • <Router>, <Route> • ϧʔςΟϯάΛߦ͏ • <Route path=“/users”

    component={Users} /> • <Link> • <a href=“…” />ͷ໾ׂ • <Link to=“/users”>show users</Link> • withRouter • ReactίϯϙʔωϯτͷpropsʹrouterػೳΛInject • withRouter(MyComponent)
  97. 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> )
  98. 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’ ͳͲ΋Մೳ
  99. 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))
  100. 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 />
  101. 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
  102. ͜͜·Ͱͷ·ͱΊ • React, Redux, Redux-thunk, React-Router • தن໛ఔ౓ͷWebΞϓϦΛߏஙͰ͖Δʢ͸ͣʣ • ςετ

    • ॻ͖·͠ΐ͏ 
  103. enzyme

  104. enzyme • air-bnb੡ReactςετϢʔςΟϦςΟ • Reactίϯϙʔωϯτςετ͕ΊͬͪΌ؆ ୯ʹॻ͚Δʂ 

  105. αϯϓϧ  // 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) }) })
  106. shallow rendering • ωετͨ͠Reactίϯϙʔωϯτ͸ͦͷ·· 

  107. 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 />)
  108. shallow rendering • ࢠίϯϙʔωϯτͷৄࡉΛແࢹͨ͠ςετ ͕ॻ͚Δ • ࢠίϯϙʔωϯτʹมߋ͕͋ͬͯ΋յΕͳ ͍ςετ • ࢠίϯϙʔωϯτ΁ͷೖྗ·ͰΛςετ

    
  109. 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') })
  110. Full rendering (mount) • DOM΁ͷ࡞༻Λςετ͢Δ৔߹ • ϥΠϑαΠΫϧϝιουͷςετ • ࢠίϯϙʔωϯτ΋ϨϯμϦϯά 

  111. 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 ') })
  112. 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ϒϥϯνࢀর
  113. ·ͱΊ • React: ViewϥΠϒϥϦ • Redux: ΞϓϦέʔγϣϯͷঢ়ଶ؅ཧ • React-Redux: ReactͱReduxͷ࿈ܞ

    • Redux-Thunk: ඇಉظॲཧ • React-Router: URLϧʔςΟϯά • enzyme: ReactίϯϙʔωϯτςετϢʔςΟϦςΟ 
  114. 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ΞϓϦΈ͍ͨͳͷΛ࡞ΕΔ 
  115. reference • Web+DB Press vol. 87 • @teppeis͞ΜͷES2015ಛू • http://www.slideshare.net/teppeis/effective-es6

    • @teppeis͞Μʹࡢ೥ฐࣾͷࣾ಺ษڧձͰൃද͍͍ͯͨͩͨ࣌͠ͷεϥΠυ • http://qiita.com/kuy • Redux, redux-sagaͷهࣄΛ୔ࢁॻ͍͍ͯΔ@kuy͞Μ • ൃԻ͸ʮΧΠʯ͞Μ 
  116. 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Ͱ੩తܕνΣοΫ  ͭ͘ΕΔˠΩϨΠʹεέʔϧ͢Δͭ͘Γʹ͢Δ
  117. ͓ΘΓ