Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
React/Redux Introduction
Search
adwd
July 08, 2016
Programming
5.3k
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.3k
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
「なぜそう決めたのか」を残し続ける仕組み ― Notion AI カスタムエージェント × Slack連携による設計判断の自動記録 - NIKKEI Tech Talk #47
niftycorp
PRO
0
260
任せる範囲はこう広がった / How the Scope of AI Delegation Has Expanded
nrslib
1
250
Mujeres en SEO Summit 2026 - Greatest Disaster Hits en Web Performance
guaca
0
240
はてなアカウント基盤 State of the Union
cockscomb
1
1.3k
Observability in Practice:Grafana 與 Edge Device SRE 的那些事
blueswen
0
190
ビデオ通話が繋がる0.2秒で何が起きているのか
supurazako
2
130
PHPだって関数型したい 〜できること、できないこと〜 / fp-in-php
jsoizo
0
190
Creating Composable Callables in Contemporary C++
rollbear
0
200
【やさしく解説 設計編 #0】DDDのコード、読めるのに分からない人へ
panda728
PRO
2
250
霧の中の代数的エフェクト
funnyycat
1
340
Vue × Nuxt × Oxc どこまで使える?実運用の現在地
andpad
0
370
Generative UI & AI-Assistants for Your Angular Solutions
manfredsteyer
PRO
1
160
Featured
See All Featured
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.8k
Principles of Awesome APIs and How to Build Them.
keavy
128
18k
SEO for Brand Visibility & Recognition
aleyda
0
4.6k
Optimizing for Happiness
mojombo
378
71k
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
118
120k
Making the Leap to Tech Lead
cromwellryan
135
10k
Exploring the relationship between traditional SERPs and Gen AI search
raygrieselhuber
PRO
2
4.1k
GitHub's CSS Performance
jonrohan
1033
470k
VelocityConf: Rendering Performance Case Studies
addyosmani
333
25k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
2.9k
What does AI have to do with Human Rights?
axbom
PRO
1
2.2k
Introduction to Domain-Driven Design and Collaborative software design
baasie
1
890
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Ͱ੩తܕνΣοΫ ͭ͘ΕΔˠΩϨΠʹεέʔϧ͢Δͭ͘Γʹ͢Δ
͓ΘΓ