Reduxã®ããŒãžã§ã³1.0ããªãªãŒã¹ããããšããç§ã¯èªåã®çµéšã«é¢ããäžé£ã®ã¹ããŒãªãŒã«å°ãæéãè²»ããããšã«ããŸããã æè¿ãã¯ã©ã€ã¢ã³ãã¢ããªã±ãŒã·ã§ã³ã«ãFlux implementationããéžæããå¿
èŠããããŸããããããã§ãReduxã§ã®äœæ¥ã楜ããã§ããŸãã
Reduxãéžã¶çç±
Reduxã¯ãJavaScriptã¢ããªã±ãŒã·ã§ã³ã®äºæž¬å¯èœãªç¶æ
ã³ã³ãããŒãšããŠã®å°äœã確ç«ããŠããŸãã ãšãã£ã¿ãŒã¯
Fluxãš
Elmã«è§ŠçºãããŠããŸãã 以åã«Fluxã䜿çšããããšãããå Žåã¯ãæ°ããïŒåªããïŒããã¥ã¡ã³ãã®ã
å
è¡ç ãã»ã¯ã·ã§ã³ã§Reduxã«å
±éç¹ãããããšãèªãããšããå§ãããŸãã
Reduxã¯ãäžé£ã®ã¢ã¯ã·ã§ã³ã«ãã£ãŠå€æŽå¯èœãªåæç¶æ
ãšããŠã¢ããªã±ãŒã·ã§ã³ãèããããšãææ¡ããŸããããã¯ãå€ãã®å¯èœæ§ãéãè€éãªWebã¢ããªã±ãŒã·ã§ã³ã«ãšã£ãŠæ¬åœã«è¯ãã¢ãããŒãã ãšæããŸãã
ãã¡ããã
ããã¥ã¡ã³ã㧠Reduxããã®ã¢ãŒããã¯ãã£ãããã³åã³ã³ããŒãã³ãã®åœ¹å²ã«é¢ãã詳现æ
å ±ãèŠã€ããããšãã§ããŸãã
ReactãšReduxã§åéãªã¹ããäœæãã
æ¬æ¥ã¯ãEditors and Reactã䜿çšããæåã®ã¢ããªã±ãŒã·ã§ã³ã®æ®µéçãªäœæã«çŠç¹ãåœãŠãŸããç°¡åãªåéãªã¹ãããŒãããäœæããŸãã
å®æããã³ãŒãã¯
ãã¡ãã§èŠã€ããããšãã§ããŸãã

誰ã®ããïŒ
ãã®èšäºã¯ãReduxã®çµéšããªã人ã察象ãšããŠããŸãã ãã©ãã¯ã¹éçºã®çµéšããªãã·ã§ã³ã§ãã æ°ããæŠå¿µã«åºäŒã£ãããããã¥ã¡ã³ããžã®ãªã³ã¯ãæäŸããŸãã
1.ã€ã³ã¹ããŒã«
Reduxã®èè
Daniil Abramovã¯ãReactãWebpackãES6 / 7ããã³React Hot Loaderã䜿çšããéçºçšã®åªãããã«ããäœæããŸããã
Reduxãã€ã³ã¹ããŒã«ããããã«ãã¯æ¢ã«ãããŸãããåã©ã€ãã©ãªã®åœ¹å²ãç解ããããšãéèŠã ãšæããŸãã
$ git clone https:
ããã§ã
httpïŒ// localhostïŒ3000ã§ã¢ããªã±ãŒã·ã§ã³ãéãããšãã§ããŸãã ã芧ã®ãšããããhello worldãã®æºåãã§ããŸããïŒ
1.1 reduxãreact-reduxãredux-devtoolsãè¿œå ããŸã
次ã®3ã€ã®ããã±ãŒãžãã€ã³ã¹ããŒã«ããå¿
èŠããããŸãã
- ReduxïŒã©ã€ãã©ãªèªäœ
- React-reduxïŒReactã®æ
- Redux-devtoolsïŒãªãã·ã§ã³ã§ãããã€ãã®äŸ¿å©ãªéçºããŒã«ãæäŸããŸãã
$ npm install --save redux@1.0.0-rc react-redux $ npm install --save-dev redux-devtools
1.2ãã£ã¬ã¯ããªæ§é
ããããè¡ãããšã¯éåžžã«ç°¡åã§ãããå®éã®ã¢ããªã±ãŒã·ã§ã³ã®ããã«ãã£ã¬ã¯ããªæ§é ãäœæããŸãããã
+-- src | +-- actions | +-- index.js | +-- components | +-- index.js | +-- constants | +-- ActionTypes.js | +-- containers | +-- App.js | +-- FriendListApp.js | +-- reducers | +-- index.js | +-- friendlist.js | +-- utils | +-- index.js +-- index.html +-- app.css
ã¢ããªã±ãŒã·ã§ã³ãäœæãããšãã«ãåãã£ã¬ã¯ããªã®åœ¹å²ã詳现ã«ç¢ºèªããŸãã App.jsãcontainersãã£ã¬ã¯ããªã«ç§»åãããããindex.jsã§importã¹ããŒãã¡ã³ããæ§æããå¿
èŠããããŸãã
1.3 Reduxã®æ¥ç¶
éçºããŒã«ãæå¹ã«ããéçºããŒã«ã«å¯ŸããŠã®ã¿devtoolsãæå¹ã«ããå¿
èŠãããããã次ã®ããã«webpack.config.jsãå€æŽããŸãã
var devFlagPlugin = new webpack.DefinePlugin({ __DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'false')) }); [...] plugins: [ new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin(), devFlagPlugin ]
DEBUG=true npm start
ã§ã¢ããªã±ãŒã·ã§ã³ãå®è¡ãããšãã¢ããªã±ãŒã·ã§ã³ã§äœ¿çšã§ãã
__DEV__
ãã©ã°ããªã³ã«ãªããŸãã 次ã®ããã«devtoolsãæ¥ç¶ã§ããŸãïŒ
import React from 'react'; import { createStore as initialCreateStore, compose } from 'redux'; export let createStore = initialCreateStore; if (__DEV__) { createStore = compose( require('redux-devtools').devTools(), require('redux-devtools').persistState( window.location.href.match(/[?&]debug_session=([^&]+)\b/) ), createStore ); } export function renderDevTools(store) { if (__DEV__) { let {DevTools, DebugPanel, LogMonitor} = require('redux-devtools/lib/react'); return ( <DebugPanel top right bottom> <DevTools store={store} monitor={LogMonitor} /> </DebugPanel> ); } return null; }
ããã§ã¯2ã€ã®ããšãè¡ã£ãŠããŸãã äœæãããé¢æ°ã䜿çšããŠcreateStoreããªãŒããŒã©ã€ãããŸããããã«ãããdevToolsãªã©ã®è€æ°ã®
ã¹ãã¢ãšã³ãã³ãµãŒã䜿çšã§ããŸãã DebugPanelãã¬ã³ããªã³ã°ããrenderDevToolsé¢æ°ãå«ãŸããŠããŸãã
ã¢ããªã±ãŒã·ã§ã³ã³ã³ãã次ã«ãApp.jsãå€æŽããŠreduxã«æ¥ç¶ããå¿
èŠããããŸãã ãã®ããã«ãreact-reduxã®
Provider
ã䜿çšã
Provider
ã ããã«ããããããã€ããŒã³ã³ããŒãã³ãã«ååšãããã¹ãŠã®ã³ã³ããŒãã³ããã¹ãã¬ãŒãžã€ã³ã¹ã¿ã³ã¹ãå©çšã§ããããã«ãªããŸãã å¥åŠãªå€èŠ³ã®é¢æ°ãå¿é
ããå¿
èŠã¯ãããŸããããã®ç®çã¯ãReacté¢æ°ã®ãã³ã³ããã¹ããã䜿çšããŠããã¹ãŠã®åïŒã³ã³ããŒãã³ãïŒãã¢ã¯ã»ã¹ã§ãããªããžããªãäœæããããšã§ãã
import React, { Component } from 'react'; import { combineReducers } from 'redux'; import { Provider } from 'react-redux'; import { createStore, renderDevTools } from '../utils/devTools'; import FriendListApp from './FriendListApp'; import * as reducers from '../reducers'; const reducer = combineReducers(reducers); const store = createStore(reducer);
ãªããžããªãäœæããã«ã¯ããã¹ãŠã®ã¬ãã¥ãŒãµãŒã®ããããšããŠãdevToolsãã¡ã€ã«ã§å®çŸ©ãã
createStore
é¢æ°ã䜿çšããŸãã
ES6æ§æ
import * as reducers
䜿çšãããšã{keyïŒfnïŒstateãactionïŒã...}ã®åœ¢åŒã§ãªããžã§ã¯ããååŸã§ããŸãã ããã¯
combineReducers
åŒæ°ãèšå®ããã®ã«
combineReducers
ã§ãã
export default class App extends Component { render() { return ( <div> <Provider store={store}> {() => <FriendListApp /> } </Provider> {renderDevTools(store)} </div> ); } }
ãã®ã¢ããªã±ãŒã·ã§ã³ã§ã¯ãApp.jsã¯Reduxã®å€éšã©ãããŒã§ãããFriendListAppã¯ã¢ããªã±ãŒã·ã§ã³ã®ã«ãŒãã³ã³ããŒãã³ãã§ãã FriendListApp.jsã§åçŽãªãHello worldããäœæããããreduxãšdevToolsã䜿çšããŠã¢ããªã±ãŒã·ã§ã³ãæçµçã«èµ·åã§ããŸãã ãããååŸããå¿
èŠããããŸãïŒã¹ã¿ã€ã«ã¯ãããŸããïŒã

ããã¯åãªããHello worldãã¢ããªã±ãŒã·ã§ã³ã§ãããããããªããŒããæå¹ã«ãªã£ãŠããŸãã ããã¹ããå€æŽããŠãç»é¢ã§èªåæŽæ°ãåãåãããšãã§ããŸãã ã芧ã®ãšãããå³åŽã®devtoolsã¯ç©ºã®ãªããžããªã瀺ããŠããŸãã ããããèšå
¥ããŠãã ããïŒ
2.ã¢ããªã±ãŒã·ã§ã³ãäœæãã
ãã¹ãŠã®èšå®ãå®äºããã®ã§ãã¢ããªã±ãŒã·ã§ã³èªäœã«éäžã§ããŸãã
2.1ã¢ã¯ã·ã§ã³ãšã¢ã¯ã·ã§ã³ãžã§ãã¬ãŒã¿ãŒ
ã¢ã¯ã·ã§ã³ã¯ãã¢ããªã±ãŒã·ã§ã³ãããªããžããªã«ããŒã¿ã転éããæ§é ã§ãã æ
£äŸã«ãããã¢ã¯ã·ã§ã³ã«ã¯ãå®è¡ããã¢ã¯ã·ã§ã³ã®ã¿ã€ãã瀺ã
type
æååãã£ãŒã«ããå¿
èŠã§ãã å¥ã®ã¢ãžã¥ãŒã«ã§ãã®ã¿ã€ããå®çŸ©ããããšã¯è¯ãç¿æ
£ã§ãããã¢ããªã±ãŒã·ã§ã³ã§äœãããããäºåã«èããããŠãããŸãã
export const ADD_FRIEND = 'ADD_FRIEND'; export const STAR_FRIEND = 'STAR_FRIEND'; export const DELETE_FRIEND = 'DELETE_FRIEND';
ã芧ã®ãšãããããã¯ã¢ããªã±ãŒã·ã§ã³ã®ç¯å²ãå®çŸ©ããéåžžã«è¡šçŸåã®ããæ¹æ³ã§ããããã«ãããå人ãè¿œå ããããããæ°ã«å
¥ãããšããŠããŒã¯ãããããªã¹ãããåé€ãããã§ããŸãã
ã¢ã¯ã·ã§ã³ãžã§ãã¬ãŒã¿ãŒã¯ã
ã¢ã¯ã·ã§ã³ãäœæããé¢æ°ã§ãã Reduxã§ã¯ãã¢ã¯ã·ã§ã³ãžã§ãã¬ãŒã¿ãŒã¯çŽç²ãªé¢æ°ã§ãããããããŒã¿ãã«ã§ãã¹ãã容æã§ãã å¯äœçšã¯ãããŸããã
ã¢ã¯ã·ã§ã³ãã©ã«ããŒã«é
眮ããŸããããããã¯ç°ãªãæŠå¿µã§ããããšãå¿ããªãã§ãã ããã
import * as types from '../constants/ActionTypes'; export function addFriend(name) { return { type: types.ADD_FRIEND, name }; } export function deleteFriend(id) { return { type: types.DELETE_FRIEND, id }; } export function starFriend(id) { return { type: types.STAR_FRIEND, id }; }
ã芧ã®ãšãããã¢ã¯ã·ã§ã³ã¯éåžžã«ãããã«ã§ãã èŠçŽ ãè¿œå ããããã«ããã¹ãŠã®ããããã£ãã¬ããŒããïŒããã§ã¯ååã®ã¿ãæ±ããŸãïŒãä»ã®ããããã£ã«ã€ããŠã¯idãåç
§ããŸãã ããè€éãªã¢ããªã±ãŒã·ã§ã³ã§ã¯ãéåæã¢ã¯ã·ã§ã³ãæ±ããããããŸããããããã¯å¥ã®èšäºã®ãããã¯ã§ã...
2.2ã¬ãã¥ãŒãµãŒ
ã¬ãã¥ãŒãµãŒã¯ãã¢ããªã±ãŒã·ã§ã³ã®ç¶æ
ãå€æŽãã責任ããããŸãã ãããã¯ã次ã®åœ¢åŒ
(previousState, action) => newState
çŽç²ãªé¢æ°
(previousState, action) => newState
ã ãªãã¥ãŒãµãŒã®åæç¶æ
ã決ããŠå€æŽããªãã§ãã ããã 代ããã«ãpreviousStateããããã£ã«åºã¥ããŠæ°ãããªããžã§ã¯ããäœæã§ããŸãã ããããªããšãããã«ããæãŸãããªãçµæãçããå¯èœæ§ããããŸãã ãŸããããã¯ã«ãŒãã£ã³ã°ãéåæåŒã³åºããªã©ã®å¯äœçšãåŠçããå Žæã§ã¯ãããŸããã
ãŸãã
initialState
ã¢ããªã±ãŒã·ã§ã³ã®ç¶æ
ã決å®ããŸãã
const initialState = { friends: [1, 2, 3], friendsById: { 1: { id: 1, name: 'Theodore Roosevelt' }, 2: { id: 2, name: 'Abraham Lincoln' }, 3: { id: 3, name: 'George Washington' } } };
ç¶æ
ã¯ç§ãã¡ãæããã®ã§ããã°äœã§ãããŸããŸãããå人ã®é
åãä¿åããã ãã§ãã ãããããã®ãœãªã¥ãŒã·ã§ã³ã¯ããŸãã¹ã±ãŒã«ããªããããå人ã®idããã³mapé
åã䜿çšããŸãã ããã«ã€ããŠã¯
normalizrã§èªãããšãã§ããŸãã
次ã«ãçŸåšã®ã¬ãã¥ãŒãµãŒãäœæããå¿
èŠããããŸãã ES6ã®æ©èœãå©çšããŠãç¶æ
ãæªå®çŸ©ã®å ŽåãåŠçããããã©ã«ãåŒæ°ãèšå®ããŸãã ããã¯ããªãã¥ãŒãµãŒã®äœææ¹æ³ãç解ããã®ã«åœ¹ç«ã¡ãŸãããã®å Žåãã¹ã€ããã䜿çšããŸãã
export default function friends(state = initialState, action) { switch (action.type) { case types.ADD_FRIEND: const newId = state.friends[state.friends.length-1] + 1; return { friends: state.friends.concat(newId), friendsById: { ...state.friendsById, [newId]: { id: newId, name: action.name } } } default: return state; } }
ES6 / 7ã®æ§æã«ç²ŸéããŠããªãå Žåãèªã¿ã«ãããããããŸããã ãªããªã ã«ãŒã«ãšããŠ
Object.assignãŸãã¯
Spread operatorã䜿çšããŠããªããžã§ã¯ãã®æ°ããç¶æ
ãè¿ãå¿
èŠããããŸãã
ããã§äœãèµ·ãããïŒæ°ããIDãå®çŸ©ããŸãã å®éã®ã¢ããªã±ãŒã·ã§ã³ã§ã¯ããµãŒããŒããååŸããããå°ãªããšãäžæã§ããããšã確èªããŸãã 次ã«ã
concat
ã䜿çšããŠããã®æ°ããIDãIDãªã¹ãã«è¿œå ããŸãã Concatã¯æ°ããé
åãè¿œå ããå
ã®é
åãå€æŽããŸããã
èšç®ãããããããã£ã¯ã
[newId]
ã䜿çšããŠfriendsByIdãªããžã§ã¯ãã«åçããŒãç°¡åã«äœæã§ããããã«ãã䟿å©ãªES6æ©èœã§ãã
ã芧ã®ãšãããæåã¯æ··ä¹±ãããããããªãæ§æã«ãããããããããžãã¯ã¯åçŽã§ãã ç¶æ
ãèšå®ããŠãæ°ããç¶æ
ã«æ»ããŸãã éèŠïŒãã®ããã»ã¹ã®ã©ã®æç¹ã§ãã以åã®ç¶æ
ãå€æŽããªãã§ãã ããã
ããŠãæ»ã£ãŠä»ã®2ã€ã®ã¢ã¯ã·ã§ã³ã®ã¬ãã¥ãŒãµãŒãäœæããŸãããã
import omit from 'lodash/object/omit'; import assign from 'lodash/object/assign'; import mapValues from 'lodash/object/mapValues'; case types.DELETE_FRIEND: return { ...state, friends: state.friends.filter(id => id !== action.id), friendsById: omit(state.friendsById, action.id) } case types.STAR_FRIEND: return { ...state, friendsById: mapValues(state.friendsById, (friend) => { return friend.id === action.id ? assign({}, friend, { starred: !friend.starred }) : friend }) }
ãªããžã§ã¯ã管çãç°¡çŽ åããããã«lodashãè¿œå ããŸããã éåžžãããã2ã€ã®äŸã§ã¯ãåã®ç¶æ
ãå€æŽããªãããšãéèŠã§ãããã®ãããæ°ãããªããžã§ã¯ããè¿ãé¢æ°ã䜿çšããŸãã ããšãã°ã
delete state.friendsById[action.id]
ã
delete state.friendsById[action.id]
代ããã«ã
_.omit
ã
_.omit
é¢æ°ã䜿çšããŸãã
ãŸããã¹ãã¬ããæŒç®åã䜿çšãããšãå€æŽããå¿
èŠãããç¶æ
ã®ã¿ãæäœã§ããããšã«æ°ä»ããããããŸããã
Reduxã¯ããŒã¿ã®ä¿åæ¹æ³ãåããªãããã
Immutable.jsã䜿çšã§ããŸãã
App.jsã§æåã§ãã£ã¹ããããåŒã³åºãããšã§ãã€ã³ã¿ãŒãã§ã€ã¹ããã€ãã¹ããŠã¹ãã¬ãŒãžãæäœã§ããŸãã
import { addFriend, deleteFriend, starFriend } from '../actions/FriendsActions'; store.dispatch(addFriend('Barack Obama')); store.dispatch(deleteFriend(1)); store.dispatch(starFriend(4));
devToolsã«ã¢ã¯ã·ã§ã³ã衚瀺ããããªã¢ã«ã¿ã€ã ã§ããããæäœã§ããŸãã

3.ã€ã³ã¿ãŒãã§ãŒã¹ãäœæãã
ãªããªã ãã®ã¬ãã¹ã³ã§ã¯ãReactã³ã³ããŒãã³ãã®äœæãã¹ãããããRedaxã®ã¿ã«çŠç¹ãåœãŠãŸããã 3ã€ã®åçŽãªã³ã³ããŒãã³ãããããŸãã
- åéãªã¹ã
- friendsïŒå人ã®é
åãé
åããŸã
FriendListItem 1ã€ã®ãã¬ã³ãèŠçŽ
- nameïŒåéã®æååå
starred: boolean
å人ããæ°ã«å
¥ããšããŠããŒã¯ãããŠããå Žåã starred: boolean
ã¯ã¢ã¹ã¿ãªã¹ã¯ã衚瀺ããŸãstarFriend: function
ãŠãŒã¶ãŒãã¢ã¹ã¿ãªã¹ã¯ãã¯ãªãã¯ãããšãã«ããªã¬ãŒãããstarFriend: function
deleteFriend: function
ãŠãŒã¶ãŒããŽãç®±ãã¯ãªãã¯ãããšãã«ããªã¬ãŒãããdeleteFriend: function
æ°ããååãå
¥åããããã®AddFriendInputãã£ãŒã«ã
addFriend: function
æŒããšããªã¬ãŒãããaddFriend: function
åŒã³åºã
Reduxã§ã¯ãå¯èœãªéãã»ãšãã©ã®ã³ã³ããŒãã³ããããã ãã«ããããšããå§ãããŸãã ã€ãŸã Reduxã«é¢é£ä»ããããŠããã³ã³ããŒãã³ããå°ãªãã»ã©è¯ãã
ããã§ã¯ã FriendListAppãå¯äžã®ãã¹ããŒããã³ã³ããŒãã³ãã«ãªããŸãã
import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import * as FriendsActions from '../actions/FriendsActions'; import { FriendList, AddFriendInput } from '../components'; @connect(state => ({ friendlist: state.friendlist })) export default class FriendListApp extends Component {
ããã¯ãdecoratorãšåŒã°ããæ°ããES7æ§æã®äžéšã§ãã ããã¯ãé«éé¢æ°ãåŒã³åºã䟿å©ãªæ¹æ³ã§ãã connect(select)(FriendListApp);
ãšåçconnect(select)(FriendListApp);
select
ã¯ãããã§è¡ã£ãããšãè¿ãé¢æ°ã§ãã
static propTypes = { friendsById: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired } render () { const { friendlist: { friendsById }, dispatch } = this.props; const actions = bindActionCreators(FriendsActions, dispatch); return ( <div className={styles.friendListApp}> <h1>The FriendList</h1> <AddFriendInput addFriend={actions.addFriend} /> <FriendList friends={friendsById} actions={actions} /> </div> ); } }
bindActionCreators
ã䜿çšããŠãã¢ã¯ã·ã§ã³ãžã§ãã¬ãŒã¿ãŒããã£ã¹ãããã§ã©ããããŸãã ç®æšã¯ããã£ã¹ããããªããžã§ã¯ããæäŸããã«ã¢ã¯ã·ã§ã³ãžã§ãã¬ãŒã¿ãŒãä»ã®ã³ã³ããŒãã³ãã«æž¡ãããšã§ãïŒããããæãã«ä¿ã¡ãŸãïŒã
次ã«èµ·ããã®ã¯ãReactã®æšæºçãªã¢ãããŒãã§ãã é¢æ°ãonClickãonChangeããŸãã¯onKeyDownããããã£ã«ãã€ã³ãããŠããŠãŒã¶ãŒã¢ã¯ã·ã§ã³ãåŠçããŸãã
ãããè¡ãæ¹æ³ã«èå³ãããå Žåã¯ãã³ãŒãå
šäœãèŠãããšãã§ããŸã ã
ããã§ãredux / reactã¢ããªã±ãŒã·ã§ã³ã®éæ³ãæããããšãã§ããŸãã GIFã«ç€ºãããŠããããã«ããã¹ãŠã®ã¢ã¯ã·ã§ã³ãèšé²ããŸãã
ããã€ãã®ã¢ã¯ã·ã§ã³ãå®è¡ãããã°ãèŠã€ããããããä¿®æ£ããä¿®æ£ãããã§ã«ä¿®æ£ãããã·ãŒã±ã³ã¹ãç¹°ãè¿ãããšãã§ããå Žåãéçºããæ¹ã䟿å©ã§ã...
翻蚳
ãªãªãžãã«èšäºhttp://www.jchapron.com/2015/08/14/getting-started-with-redux/