Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
React/Redux Introduction
Search
adwd
July 08, 2016
Programming
5.4k
17
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
React/Redux Introduction
adwd
July 08, 2016
More Decks by adwd
See All by adwd
GraphQLのあまり知られていない魅力 (スキーマの表現力編) / domain modeling with GraphQL Schema
adwd
5
6.4k
RxJSで状態を管理する / state management by RxJS
adwd
1
1.3k
Savkin先生から学んだぼくの考えた最強のAngularアプリアーキテクチャ / The best Angular application architecture
adwd
1
1.2k
create-react-app-introduction
adwd
7
2k
Other Decks in Programming
See All in Programming
How I Stole PSI from Android Studio - DroidKaigi2026
worker8
0
130
Webの地図
yosuke_furukawa
PRO
6
4.5k
iOSDC Japan 2026 - Swiftで作って学ぼう!データベース自作入門
kaseken
2
160
大喜利で理解するLLM as a Judge / Understanding LLM-as-a-Judge through Ogiri
rockname
0
100
Security issues being discussed on Web Platforms
petamoriken
0
940
テストを司るデーモンに会いに行く 〜隔離した仮想マシンでテストを通すまで〜
h1d3mun3
1
360
一人だけ、Kiroが静止する日
hideg
0
110
DroidKaigi 2026 「個人開発という実験場: Android エンジニアが手にする4つの自由」
slashnephy
0
230
『寄り添うラジオ』をAIで作る 体験価値から逆算した、会話しないUXと品質設計
theoriatec2024
3
180
FreeBSDでZabbixを動かす.pdf
kenkino
0
280
Intent as Code
shoppingjaws
6
1k
技術的負債を組織課題として解く-増えすぎたマイクロサービスとの戦い-
reimaru
1
1.8k
Featured
See All Featured
Effective software design: The role of men in debugging patriarchy in IT @ Voxxed Days AMS
baasie
1
520
[SF Ruby Conf 2025] Rails X
palkan
3
1.4k
Rails Girls Zürich Keynote
gr2m
96
14k
Introduction to Domain-Driven Design and Collaborative software design
baasie
1
990
Build The Right Thing And Hit Your Dates
maggiecrowley
39
3.4k
We Are The Robots
honzajavorek
0
370
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.8k
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
68
57k
HTML-Aware ERB: The Path to Reactive Rendering @ RubyCon 2026, Rimini, Italy
marcoroth
5
650
Docker and Python
trallard
47
4.2k
Six Lessons from altMBA
skipperchong
29
4.5k
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
410
Transcript
React/Redux Introduction 2016/7/8
ࣗݾհ ాխത 2015/05ʹBizreachʹస৬ɺWebܦݧ1 ٻਓݕࡧΤϯδϯελϯόΠͷ͚ࣾཧ ը໘ΛReact/ReduxͰ։ൃத https://github.com/adwd https://twitter.com/adwd118
None
None
ελϯόΠཧը໘ද 2015/5 ~ 2015/5 jQuery 2015/7 AngularJS 1 2015/12
React/Redux
Βͳ͍͜ͱ • React/Reduxͷৄ͍͠આ໌ • React vs Angular, facebook/flux vs redux
..
Δ͜ͱ • React/ReduxͰTodoΞϓϦҎ্ͷͷΛ࡞Δ ͨΊʹඞཁͳཁૉͷβοͱͨ͠આ໌ • React, Redux, react-redux, react-router •
TodoΞϓϦΛ࡞ΕΔʢؾ͕͢ΔʣΑ͏ʹͳΔ ͷ͕ΰʔϧ
Ξϯέʔτ • ϓϩάϥϛϯάɾjavascriptະܦݧ • ϓϩάϥϛϯάɾjavascriptνϣοτϫΧϧʢॳ৺ऀʣ • React/ReduxͷνϡʔτϦΞϧΛͬͨʢΖ͏ͱͨ͠ʣ • React/ReduxνϣοτσΩϧʢීஈ͔ΒͬͯΔʣ
Ξϯέʔτ̎ • ඇΤϯδχΞ • σβΠφʔ • ϑϩϯτΤϯυΤϯδχΞ • ϞόΠϧΞϓϦΤϯδχΞ •
ͦΕҎ֎ʢόοΫΤϯυͱ͔Πϯϑϥͱ͔ʣ
ίϯςϯπ • श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
Redux DevTools • Reduxͷ։ൃʹศརͳChrome֦ு • https://chrome.google.com/webstore/ detail/redux-devtools/ lmhkpmbekcpmknklioeibfkpmmfibljd
͜͜Ͱը໘Λ͞ΘͬͯΈΔ
ESLintͷεεϝ • javascriptͷจ๏ΛνΣοΫͯ͘͠ΕΔ • ΤσΟλͱ࿈ܞ͢Δͷ͕Φεεϝ • eslint —fix Ͱ͋Δఔͷमਖ਼ͬͯ͘ΕΔ •
νʔϜ։ൃͰίʔυϨϏϡʔָ͕ʹͳΔ
ESLintͷεεϝ • ྫɿ Atom + linter-eslint
React
React • ViewϥΠϒϥϦ • js-centric: શ෦jsͷதʹॻ͘ • ίϯϙʔωϯτࢦ
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ʹೖΔ
React sample • localhost:3000/react • src/views/ReactSample/TodoList.js • src/views/ReactSample/TodoItem.js
αϯϓϧΛ͞ΘͬͯΈΔ
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
React ·ͱΊ • ViewϥΠϒϥϦ • ͳͷͰMVCͰ͍͏Model, Controllerʹ͋ͨΔػೳ͕ͳ͍ • this.state, this.setStateͰෳࡶͳঢ়ଶΛཧ͢Δͷ…
→ Redux • ࣭ͱ͔ʁ
Redux
Redux • FluxΞʔΩςΫνϟͷ࣮ͷҰͭ • Flux࣮facebook/flux͔reduxͷͲͪΒ͔ͱ͍͏ งғؾʁ • υΩϡϝϯτ͕ॆ࣮ http://redux.js.org •
࣮͕300ߦ͔͠ͳ͍ͷͰಡΉͷΞϦ • ΞϓϦέʔγϣϯͷঢ়ଶΛҰͭͷJSONͰදݱ͢Δ
୯ํσʔλϑϩʔ IUUQXXXTMJEFTIBSFOFU+POBT0IMTTPOVTJOHSFEVY
୯ํσʔλϑϩʔ • ViewStoreͷঢ়ଶΛඳը͢Δ͚ͩ • ঢ়ଶΛมߋ͢ΔͷAction͚ͩ • Reducer(currentState, action) => nextStateͳ
ͨͩͷؔ
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 • ΞϓϦέʔγϣϯͷঢ়ଶΛอ࣋͢Δ • StoreΞϓϦέʔγϣϯதʹͻͱͭ • ΞϓϦέʔγϣϯͷঢ়ଶ1ຕͷJsonʹͳΔ
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
react-redux • ReactͱReduxΛͭͳ͙ • ReduxͷStoreͱReactͷprops • ReduxͷΞΫγϣϯͱReactͷΠϕϯτ
react-redux • ReactͱReduxΛͭͳ͙ • ReactίϯϙʔωϯτͷpropsʹɺRedux StoreͷstateͱdispatchΛInject͢Δ
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 • API2͚ͭͩ • connect • Provider
react-redux: connect • Redux Storeͷstate͔Βඞཁͳ෦ΛReact ίϯϙʔωϯτͷpropsʹೖ͢ΔηϨΫλ
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 • connectΛݺΔΑ͏ʹ͢Δ • ReactΞϓϦέʔγϣϯͷϧʔτίϯϙʔ ωϯτΛProviderͰϥοϓ
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') )
Reduxαϯϓϧ • http://localhost:3000/redux • src/views/ReduxSample/ReduxSample.js • src/redux/modules/todo.js
Redux DevTools • ActionͱStateΛ࣌ܥྻͰදࣔ • աڈͷStateʹߦͬͨΓͰ͖Δ
αϯϓϧΛ͞ΘͬͯΈΔ
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ͰϧʔτͷίϯϙʔωϯτΛϥοϓ • ࣭ͳͲʁ
͜͜·Ͱͷ·ͱΊ • React • Reduxɺreact-redux • TodoΞϓϦΛ࡞Δ͜ͱ͕Ͱ͖ͦ͏
͔͜͜Β • ඇಉظॲཧ • Web APIΛୟ͘Α͏ͳॲཧ • ϧʔςΟϯά • /users/1
• ςετ
ඇಉظॲཧ
ඇಉظαϯϓϧ • http://localhost:3000/redux-async • src/views/ReduxAsyncSample/ • src/modules/asyncTodo/asyncTodo.js
ඇಉظαϯϓϧ 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ΞΫηεঢ়ଶΛஔ͍͓ͯ͘ • ࣭ͳͲʁ
react-router
react-router • URLભҠͱReactίϯϙʔωϯτදࣔΛಉظ ͢Δ • src/routes/index.js
ओͳ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
͜͜·Ͱͷ·ͱΊ • React, Redux, Redux-thunk, React-Router • தنఔͷWebΞϓϦΛߏஙͰ͖Δʢͣʣ • ςετ
• ॻ͖·͠ΐ͏
enzyme
enzyme • air-bnbReactςετϢʔςΟϦςΟ • Reactίϯϙʔωϯτςετ͕ΊͬͪΌ؆ ୯ʹॻ͚Δʂ
αϯϓϧ // 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 • ωετͨ͠Reactίϯϙʔωϯτͦͷ··
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 • ࢠίϯϙʔωϯτͷৄࡉΛແࢹͨ͠ςετ ͕ॻ͚Δ • ࢠίϯϙʔωϯτʹมߋ͕͋ͬͯյΕͳ ͍ςετ • ࢠίϯϙʔωϯτͷೖྗ·ͰΛςετ
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) • DOMͷ࡞༻Λςετ͢Δ߹ • ϥΠϑαΠΫϧϝιουͷςετ • ࢠίϯϙʔωϯτϨϯμϦϯά
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Ͱ੩తܕνΣοΫ ͭ͘ΕΔˠΩϨΠʹεέʔϧ͢Δͭ͘Γʹ͢Δ
͓ΘΓ