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
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
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
thread_parallel_with_free-threaded_Python_and_NumPy.pdf
riku_sakamoto
0
360
iOSDC2026登壇資料.pdf
riofujimon
0
190
ハーネス設計入門 〜 基礎知識の整理から実務へのステップアップ 〜
kinopeee
18
18k
AgentCore CLI で進化した AWS での AI エージェントの作り方 : 必要な機能を必要な時に
icoxfog417
PRO
3
390
Omarchy Tokyo やると聞いて UMPC 買ってセットアップしてきた
mtsmfm
0
170
Starting & Sustaining Code-Based E2E Testing for Non-Coding QA Teams( #jasstniigata )
teyamagu
PRO
1
590
Everything will be SERVERLESS — 信じて運用した10年の経験値 / Everything Will be Serverless — Lessons Learned from 10 Years of Operational Experience
seike460
PRO
1
540
手動確認はもう限界 〜XCUITestでCustom URL Schemeの遷移を起動種別ごとに自動テストする〜 / Testing Custom URL Schemes with XCUITest
otouto
0
350
速習iPhone Duo対応
yuukiw00w
2
840
新卒PdEのリアル
ryu1013
1
520
難しいけど、読めた。- OSSの入口に立った話。
sts11142
0
130
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
310
Featured
See All Featured
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
350
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.4k
Lightning talk: Run Django tests with GitHub Actions
sabderemane
0
270
Imperfection Machines: The Place of Print at Facebook
scottboms
270
14k
Mind Mapping
helmedeiros
1
370
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
900
Joys of Absence: A Defence of Solitary Play
codingconduct
1
530
Unlocking the hidden potential of vector embeddings in international SEO
frankvandijk
0
950
Test your architecture with Archunit
thirion
2
2.4k
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
1
6.2k
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
770
The untapped power of vector embeddings
frankvandijk
2
1.9k
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Ͱ੩తܕνΣοΫ ͭ͘ΕΔˠΩϨΠʹεέʔϧ͢Δͭ͘Γʹ͢Δ
͓ΘΓ