ã§ããããããªãã¯Todoãªã¹ãã倧奜ãã§ããããšãã€ãŸãTodoãªã¹ããæžãããšãæ¬åœã«å¥œãã§ããããšãç¥ã£ãŠããã®ã§ãæ°ããããããããæ é€äŸ¡ã®é«ã
ïŒãæ é€äŸ¡ã®é«ããPerãïŒ Fluxãã¬ãŒã ã¯ãŒã¯ã䜿çšããŠãªã¹ããäœæããŠãã ããã
Redux ïŒ ç§ã¯ããªãã«æé«ã®ããšãé¡ã£ãŠããŸãã
ãã®èšäºã§ã¯ãç¬èªã®Reduxã¢ããªã±ãŒã·ã§ã³ããã«ãããæ¹æ³ãåŠã³ãŸãã
- å
šç²ãµãŒããŒã¬ã³ããªã³ã°
- é«åºŠãªã«ãŒãã£ã³ã°ãRich Omega-3
- æ²¹ã£ãœãéåæããŒã¿ã®èªã¿èŸŒã¿
- æ»ããã§æ©èœçãªä»äžã
ãããããªãããã®äººçã§æããã®ã®ããã«èŠãããªããã«ããã®äžã«é²ãã§ãã ãããããã§ãªããªããæ°ã«ããªãã§ãã ããã
ãäŒããããã®ã§ãã...ããã¯éåžžã«å°ããªãã¥ãŒããªã¢ã«ã§ã¯ãããŸããã®ã§ãæã䌞ã°ããŠåä»ãªéã®æºåãããŠãã ãããè
ãèãçªãåºããªãã§ãã ãã...
åæ¢ããŠãåŸ
ã£ãŠãReduxãšã¯äœã§ããïŒ
ãããç§ã¯ããªããå°ããŠããããã§ãïŒ
Reduxã¯
Danil Abramovã®æ°ããFluxãã¬ãŒã ã¯ãŒã¯ã§ãå€ãã®äžå¿
èŠãªå°é£ãåãé€ããŸãã
ããã§ãã®ãã¬ãŒã ã¯ãŒã¯ãäœæãããçç±ãèªãããäžè¬çã«TLã
åç
§ããŠãã ãã ïŒDR
Reduxã¯ã¢ããªã±ãŒã·ã§ã³ã®ç¶æ
ã1ãæã«ä¿æãããã®ç¶æ
ãšå¯Ÿè©±ããããã®æå°éã§ãããªããéåžžã«åŒ·åãªæ¹æ³ãå®çŸ©ããŸã
ïŒç¶æ
origãïŒ ã
åŸæ¥ã® Flux ãã¬ãŒã ã¯ãŒã¯ã«ç²ŸéããŠããå Žåãæ倧ã®éãã¯ãVaults
ïŒStores origãïŒã®äžè¶³ãšãReducer
ïŒReducers origãïŒã®ååšã§ãã
Reduxã§ã¯ãã¢ããªã±ãŒã·ã§ã³ã®ç¶æ
å
šäœãå¥ã
ã®ãªããžããªã«åå²ãããã®ã§ã¯ãªãã1ã€ã®å ŽæïŒReduxã®ã€ã³ã¹ã¿ã³ã¹ïŒã«ååšããŸãïŒããã¯å°ãååã«æµæã§ããŸãïŒã
ãReducerãã¯ãç¶æ
ãã©ã®ããã«å€åãããã®èª¬æã§ãããæ¬è³ªçã«ã¯äœãå€åããã次ã®ããã«ãªããŸãã
function exampleReducer(state, action) { return state.changedBasedOn(action) }
ã©ããã£ãŠïŒ å°ãåŸã§èŠãã§ãããã
Reduxã¯ãã¬ãŒã ã¯ãŒã¯ãšããããããœãªã¥ãŒã·ã§ã³ã®ãããªãã®ã ãšæãã§ãããã ããã¯ããã¹ãŠã®æé«ã®Fluxã¢ã€ãã¢ãå«ãæå°ããŒã¹ã§ãã ãã®èšäºã¯ãããªãããããããŸã䜿ããç¹æ ããæå°éã®çªç¶å€ç°ã§é²ãããšãããªãã«æããããšãé¡ã£ãŠããŸãã
èªåãå¿«é©ã«ãã
Webpackãš
Babelã䜿çšããŠã¢ããªã±ãŒã·ã§ã³ããã³ãã«ããŸãããªããªããç§ãã¡ã¯ã¯ãŒã«ã§ãã¹ããŒãã§ã楜ããããããŠ
ã³ãŒãããã®å Žã§ãªããŒãããŠææ°ã®ES6 / 7ããããæäŸã§ããããã§ãã
ãŸãããã£ã¬ã¯ããªãäœæããããã«ããã€ãã®ãã¡ã€ã«ãé
眮ããå¿
èŠããããŸãã
äºåã«æºåãã
package.jsonã¯æ¬¡ã®ãšããã§ãã
package.json { "name": "isomorphic-redux", "version": "1.0.0", "description": "Basic isomorphic redux application", "main": "index.js", "scripts": { "start": "NODE_PATH=$NODE_PATH:./shared node .", "dev": "npm run start & webpack-dev-server --progress --color" }, "author": "<your-name> <<your-email>>", "license": "MIT", "dependencies": { "axios": "^0.5.4", "express": "^4.13.2", "immutable": "^3.7.4", "object-assign": "^3.0.0", "react": "^0.13.3", "react-redux": "^0.2.2", "react-router": "^1.0.0-beta3", "redux": "^1.0.0-rc" }, "devDependencies": { "babel": "^5.8.20", "babel-eslint": "^4.0.5", "babel-loader": "^5.3.2", "eslint": "^1.0.0", "eslint-plugin-react": "^3.1.0", "react-hot-loader": "^1.2.8", "webpack": "^1.10.5", "webpack-dev-server": "^1.10.1" } }
åæ§ã«
webpack.config.js var path = require('path'); var webpack = require('webpack'); module.exports = { entry: [ 'webpack-dev-server/client?http://localhost:8080/', 'webpack/hot/only-dev-server', './client' ], output: { path: path.join(__dirname, 'dist'), filename: 'bundle.js' }, resolve: { modulesDirectories: ['node_modules', 'shared'], extensions: ['', '.js', '.jsx'] }, module: { loaders: [ { test: /\.jsx?$/, exclude: /node_modules/, loaders: ['react-hot', 'babel'] } ] }, plugins: [ new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin() ], devtool: 'inline-source-map', devServer: { hot: true, proxy: { '*': 'http://localhost:' + (process.env.PORT || 3000) } } };
ããã³
.babelrc ïŒãES7ãã·ã¥ã¬ãŒçšïŒ
{ "optional": ["es7.decorators", "es7.classProperties", "es7.objectRestSpread"] }
ççŽã«èšã£ãŠããããã®ãã¡ã€ã«ã¯ããã»ã©æ³šç®ã«å€ãããã®ã§ã¯ãªããåççãªéçºç°å¢ãäœæããã ãã§ãã
OKã
npm i
ãå®è¡ããŠãã¹ãŠã®äŸåã¢ãžã¥ãŒã«ãããŠã³ããŒãããå¿
èŠããããéå§ã§ããŸãã
ã·ãŒã¢ã¢ããã ãã
ã¢ããªã±ãŒã·ã§ã³ã®åºæ¬æ§é ã¯æ¬¡ã®ããã«ãªããŸãã
client/ shared/ index.js server.jsx
ã³ãŒãã®äž»èŠéšåã¯ãã¹ãŠ
shared
ãã£ã¬ã¯ããªã«ãããŸãããã¯ã©ã€ã¢ã³ãéšåãšãµãŒããŒéšåãåé¢ããã«ã¯ãããã€ãã®æ¥çã³ãŒããå¿
èŠã§ãã
index.js 'use strict'; require('babel/register')({}); var server = require('./server'); const PORT = process.env.PORT || 3000; server.listen(PORT, function () { console.log('Server listening on', PORT); });
server.jsxãå®è¡ãããã¡ã€ã«ã ããªã®ã§ãES6 / JSXã䜿çšã§ããŸãã
ãµãŒããŒã®æ©èœã¯
Expressã«ãã£ãŠå®è¡ãããŸããããã¯ããµãŒããŒãããåçŽã§ãããæ¢ã«ç¥ã£ãŠããå¯èœæ§ãããããã§ãã
server.jsx import express from 'express'; const app = express(); app.use((req, res) => { const HTML = ` <DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Isomorphic Redux Demo</title> </head> <body> <div id="react-view"></div> <script type="application/javascript" src="/bundle.js"></script> </body> </html> `; res.end(HTML); }); export default app;
ããªãæšæºçãªã§ãããã ã°ããŒãã«ããã«ãŠã§ã¢ã§ExpressãµãŒããŒãã»ããã¢ããããŸãããäœãåŠçãããååšã®ç©ºçœã調ã¹ãã®ã«åœ¹ç«ã€ç©ºã®WebããŒãžã®ã¿ãåŠçããŸãã ãããä¿®æ£ããŸãããã
ã«ã€ããŠã®ã«ãŒãã³
ããããããšã¯ã¹ãã¬ã¹ã«ãŒãã£ã³ã°ãšãã³ãã¬ãŒãã䜿çšããããšã¯ç°¡åã ãšæããŸãã æ®å¿µãªããããµãŒããŒãšã¯ã©ã€ã¢ã³ãéã§ã§ããã ãå€ãã®ããžãã¯ãå
±æãããã®ã§ãããªãã¯ééã£ãŠããŸãã ã¯ã©ã€ã¢ã³ããšãµãŒããŒã§ã«ãŒãã£ã³ã°ã§ããããã
React Routerã䜿çšããŸãã
ãã®ãããReact Routerãåã蟌ãŸããã«ãŒãã³ã³ããŒãã³ã
shared/components/index.jsx
ããããŸãã ãã®ããã«ããŠãã¢ããªã±ãŒã·ã§ã³å
šäœïŒãã£ããââãå°äžå®€ãªã©ïŒã«çŸåŠãè¿œå ããããšãã§ããŸããçŽ æŽãããSPAã«é©ããã¢ãŒããã¯ãã£ã§ãã
å
±æ/ã³ã³ããŒãã³ã/index.jsx import React from 'react'; export default class AppView extends React.Component { render() { return ( <div id="app-view"> <h1>Todos</h1> <hr /> {this.props.children} </div> ); } }
ããã®
åã¯ã³ã³ããŒãã³ãããªãŒã«å€ãããäŸåé¢ä¿ãæã€éæ³ã®åŸã«ã«ãŒã¿ãŒãæäŸããŸãã ããã«ã¯ç¹å¥ãªãã®ã¯ãããŸããããã¹ãŠããã®ãŸãŸå°å·ããã ãã§ãã
次ã«ãã«ãŒããå®çŸ©ããå¿
èŠããããŸã
shared / routes.jsx import React from 'react'; import { Route } from 'react-router'; import App from 'components'; export default ( <Route name="app" component={App} path="/"> </Route> );
ããã§ã¯ã '/'ãã¹ã«æ²¿ã£ãŠ
components/index
ã衚瀺ããããã«React Routerã«æ瀺ããŸãã ããã§ããïŒ
次ã«ããµãŒããŒã§åãããšãè¡ããŸãã
server.jsx import React from 'react'; import { Router } from 'react-router'; import Location from 'react-router/lib/Location'; import routes from 'routes'; app.use((req, res) => { const location = new Location(req.path, req.query); Router.run(routes, location, (err, routeState) => { if (err) return console.error(err); const InitialComponent = ( <Router {...routeState} /> ); const componentHTML = React.renderToString(InitialComponent); const HTML = `...`; res.end(HTML); }); });
ããã§ãããã€ãã®æ°ããããã¡ããã€ã³ããŒãããè¡šçŸããèŠæ±ã転éããããã«ã«ãŒã¿ãŒã«æ瀺ããŸãã
routeState
å€æ°ã«æ»ããèŠæ±ãããã«ãŒãã衚瀺ã§ããããšãé¡ã£ãŠããŸãã 次ã«ã
Reactã® smart
renderToString
ã¡ãœããã䜿çšããŠãã³ã³ããŒãã³ããHTMLæååã§åºåããŸããããã¯ãåã«äœæãã
react-view divã§ã¯ã©ã€ã¢ã³ãã«æäŸããŸãã
<div id="react-view">${componentHTML}</div>
npm start
ãå®è¡
npm start
httpïŒ// localhostïŒ3000 /ã«ãŒããHTMLã«æ¿å
¥ãããããšãããããŸãã

ããã€ãã®ãšã©ãŒãã³ã³ãœãŒã«ã«è¡šç€ºãããã«ãŒã¿ãŒã«è€æ°ã®å€ãå¿
èŠã§ããããšã瀺ããŠããŸãã ããã¯ãã¯ã©ã€ã¢ã³ãã
bundle.js
ãããŠã³ããŒãããããšããŠããããã§ããããŸã webpackã®ãšã³ããªãã€ã³ããèšå®ããŠããªããããããã¯ãã®ãããªãŽãã§ãã
èŠãç®ã¯çŽ æŽããããããªãã®è³ã¯ãã§ã«å£ããŠãããšæããŸãããä»ã¯éçãªããŒãžããããŸãã Reactãããã¹ãŠã®ãžã¥ãŒã·ãŒãªãã«ããå
¥æããã«ã¯ãã¯ã©ã€ã¢ã³ãã«ã«ãŒãã£ã³ã°ãå®è£
ããå¿
èŠããããŸãã
ã ããã
client/index.jsx
ãéããŠäœããæžããŠãã ããïŒ
ã¯ã©ã€ã¢ã³ã/ index.jsx import React from 'react'; import { Router } from 'react-router'; import { history } from 'react-router/lib/BrowserHistory'; import routes from 'routes'; React.render( <Router children={routes} history={history} />, document.getElementById('react-view') );
Routerã³ã³ããŒãã³ãã
react-view divã«æ¿å
¥ããããã«Reactã«æ瀺ããé©åãªãã©ã¡ãŒã¿ãŒãæž¡ããŸããã ãµãŒããŒéšåã«ã¯è¡šç€ºãããªãã£ã
å±¥æŽãªããžã§ã¯ããReact Routeræ§æã®å¿
èŠãªéšåïŒçŽæ¥è¡šç€ºãããå ŽåïŒãããã³URLã®å€èŠ³ã«ã€ããŠèª¬æããŸãã ãããŠãç§ãã¡ã¯ããããªæœèšãæ±ããŠããŸãïŒ ãããã£ãŠã
BrowserHistoryã§
HTML5 History APIã䜿çšã
ãŸãããå€ããã©ãŠã¶ãŒã§ã¯
HashHistoryã䜿çšããŠãã¢ãã¬ã¹ãã
/#
URLãååŸã§ããŸãã
ããã§ã
npm run dev
ã¢ããªã±ãŒã·ã§ã³ã
npm run dev
ã§ããWebpackãbundle.jsãåŠçããŸãã ããŸãããããããã§ã¯ãããŸãããã
httpïŒ// localhostïŒ8080 /ã«ã¢ã¯ã»ã¹ãããšããšã©ãŒãªãåäœããã¯ãã§ãã ã«ãŒãã£ã³ã°ãå®äºããReduxã¢ã¯ã·ã§ã³ã®æºåãã§ããŸããã
åæžãåå©çšããªãã¥ãŒã¹
Reduxã¯Fluxã«éåžžã«ãã䌌ãŠããŸããã以åã«ã¹ãã¬ãŒãžã®ä»£ããã«ãªãã¥ãŒãµãŒã䜿çšãããããšãè¿°ã¹ãŸããã æåã«ãTodoã·ãŒããå€æŽããç°¡åãªæé ãããã€ãäœæããŸãã
å
±æ/ã¢ã¯ã·ã§ã³/ TodoActions.js export function createTodo(text) { return { type: 'CREATE_TODO', text, date: Date.now() } } export function editTodo(id, text) { return { type: 'EDIT_TODO', id, text, date: Date.now() }; } export function deleteTodo(id) { return { type: 'DELETE_TODO', id }; }
ã芧ã®ãšãããReduxã§ã¯ã
ã¢ã¯ã·ã§ã³ã¯ãªãšãŒã¿ãŒã¯ãé çªã«ãã©ãŒãããããããªããžã§ã¯ããè¿ãåãªãé¢æ°ã§ãã éæ³ã§ã¯ãããŸããããããåŠçããããã«æžéæ©ãå¿
èŠã§ãã
å
±æ/ã¬ãã¥ãŒãµãŒ/ TodoReducer.js import { List } from 'immutable'; const defaultState = new List(); export default function todoReducer(state = defaultState, action) { switch(action.type) { case 'CREATE_TODO': return state.concat(action.text); case 'EDIT_TODO': return state.set(action.id, action.text); case 'DELETE_TODO': return state.delete(action.id); default: return state; } }
ãã¹ãŠãåã³åçŽã§ãã ããã§ã
Immutable Listãªããžã§ã¯ãã䜿çšããŠãäžå€ã®ç¶æ
ããªããžããªã«æ ŒçŽãïŒãã倧ããªã¢ããªã±ãŒã·ã§ã³ã§ã¯ããè€éã«ãªãå¯èœæ§ããããŸãïŒãã¢ã¯ã·ã§ã³ã«å¿ããŠç¶æ
ã®æ°ããããŒãžã§ã³ãè¿ãããšãã§ããŸãã
Reduxã¯ããã»ã©é åºã§ã¯ãªããã¬ãã¥ãŒãµãŒããã®æåŸ
ã¯2ã€ã ãã§ãã
- 眲å
(state, action) => newState
ã§ãã - ã¬ãã¥ãŒãµãŒã¯æž¡ãããç¶æ
ãå€æŽããŸããããæ°ããããŒãžã§ã³ãè¿ããŸãã
ã芧ã®ãšãããåŸè
ã¯
Immuatable.jsã«é©ããŠããŸãããã§ã¯åçŽãªã¹ã€ããæ§é ã䜿çšããŸãããæ°ã«å
¥ããªãå Žåã¯ã空çœãæžãããšããããããªãã§ãã ããã
Reduxã¯åäžã®ãªãã¥ãŒãµãŒã®äœ¿çšã«éå®ãããŸãããããããååŸããããã«ãreducer
reducers/index.js
äœæã§ããŸãã
export { default as todos } from './TodoReducer';
å
šå¡ãæã£ãŠããã®ã§ãå®éã«ã¯å¿
èŠãããŸããããå°æ¥å¿
èŠã«ãªããŸãã
IIIiiii ...ã¢ã¯ã·ã§ã³ïŒ
ã¬ãã¥ãŒãµãŒãšã¢ã¯ã·ã§ã³ã«ã€ããŠè©±ãã®ã¯è¯ãããšã§ãããã¢ããªã¯ããã«ã€ããŠäœãç¥ããŸããïŒ ãããå€ããæãæ¥ãŸããã
Reduxã€ã³ã¹ã¿ã³ã¹ãã³ã³ããŒãã³ãããªãŒã«ããã·ã¥ããŠãããããã¹ãŠãåŠçããããããã¹ãŠããªã³ã¯ããå¿
èŠããããŸãïŒ
NPMã®
react-redux
ã¯ãããã«åœ¹ç«ã€ããã€ãã®
react-redux
ã
server.jsx import { createStore, combineReducers } from 'redux'; import { Provider } from 'react-redux'; import * as reducers from 'reducers'; app.use((req, res, next) => { const location = new Location(req.path, req.query); const reducer = combineReducers(reducers); const store = createStore(reducer); Router.run(routes, location, (err, routeState) => { if (err) return console.error(err); const InitialView = ( <Provider store={store}> {() => <Router {...routeState} /> } </Provider> );
ãªã¯ãšã¹ãããšã«Reduxã¹ãã¬ãŒãžã³ã³ããŒãã³ãã®ã€ã³ã¹ã¿ã³ã¹ãäœæãã
Provider
ã«ãŒãã³ã³ããŒãã³ããã©ããããããšã«ãããã³ã³ããŒãã³ãããªãŒå
šäœïŒã¢ã¯ã»ã¹ãå¿
èŠãªå Žåã¯
<component> .context.reduxãšããŠå©çšå¯èœïŒã«
転éã
Provider
ã
ã¯ã©ã€ã¢ã³ãã«åæç¶æ
ãäžããŠãä¿ç®¡æœèšãæ°ŽçŽ åã§ããããã«ããå¿
èŠããããŸãã
Reduxã®ç¶æ
ãå°ããã ãã§ãïŒ
const initialState = store.getState();
ãããŠãHTMLãã³ãã¬ãŒãã«æ°è¡è¿œå ããŸãã
<title>Redux Demo</title> <script type="application/javascript"> window.__INITIAL_STATE__ = ${JSON.stringify(initialState)}; </script>
ãã®åŸã
window.__INITIAL_STATE__
ä»ããŠã¯ã©ã€ã¢ã³ãã®ç¶æ
ã«ã¢ã¯ã»ã¹ã§ããããã«ãªããŸãã
ä»ãããªããã°ãªããªãããšã¯ããã¹ãŠã
Immutable.jsã³ã¬ã¯ã·ã§ã³ã«å€æããããšã§ãã ãããŠãæ°ãããªããžããªãã€ã³ã¹ã¿ã³ã¹åãããšãã«ããããReduxã«æž¡ããŸãã
ã¯ã©ã€ã¢ã³ã/ index.jsx import { createStore, combineReducers } from 'redux'; import { Provider } from 'react-redux'; import * as reducers from 'reducers'; import { fromJS } from 'immutable'; let initialState = window.__INITIAL_STATE__;
ããã¯ããµãŒããŒããæž¡ãããç¶æ
ã§ã¹ãã¬ãŒãžããã€ãã¬ã€ãããããšãé€ããŠããµãŒããŒã®ç¶æ
ãåæåããããšãšåãã§ãã
æ¬åœã«ã¢ããªã±ãŒã·ã§ã³ã®å®æã«è¿ã¥ããŠããŸãããã¹ãŠã®ãã€ã³ããæ¥ç¶ããããã«ãããã€ãã®ã³ã³ããŒãã³ããæ®ã£ãŠããŸãã
ãã¹ãŠã®ãã€ã³ããæ¥ç¶ãã
3ã€ã®ã³ã³ããŒãã³ãã䜿çšããŠãå°ãåé·ãªæ
å ±ã衚瀺ããŸãïŒã»ãšãã©ã®å Žåããã§ãïŒããããã¯ãã¹ããŒããã³ã³ããŒãã³ããšããã ãã³ã³ããŒãã³ãã®Reduxã®éãã瀺ããŸããããã¯å€§èŠæš¡ã¢ããªã±ãŒã·ã§ã³ã§éåžžã«éèŠã§ãã
ã¹ããŒãã³ã³ããŒãã³ãã¯Reduxã¹ãã¬ãŒãžã€ãã³ãããµãã¹ã¯ã©ã€ããïŒããšãã°ã
@ connectorãã³ã¬ãŒã¿æ§æã䜿çšïŒãããããã£ãä»ããŠä»ã®ã³ã³ããŒãã³ãã«ããªãŒãããã·ã¥ããŸãã ããªãŒã®ã©ãã«ã§ãé
眮ã§ããŸãããããè€éãªã¢ããªã±ãŒã·ã§ã³ãéçºããå Žåãéåžžã¯æäžå±€ã«å°éããŸãã
ããã§ã¯1ã€ã ãã䜿çšããŸã
å
±æ/ã³ã³ããŒãã³ã/ Home.jsx import React from 'react'; import TodosView from 'components/TodosView'; import TodosForm from 'components/TodosForm'; import { bindActionCreators } from 'redux'; import * as TodoActions from 'actions/TodoActions'; import { connect } from 'react-redux'; @connect(state => ({ todos: state.todos })) export default class Home extends React.Component { render() { const { todos, dispatch } = this.props; return ( <div id="todo-list"> <TodosView todos={todos} {...bindActionCreators(TodoActions, dispatch)} /> <TodosForm {...bindActionCreators(TodoActions, dispatch)} /> </div> ); } }
次ã«ã2ã€ã®ããã ãã³ã³ããŒãã³ããäœæããŸããããŸããããã§äœãèµ·ããããèŠãŠã¿ãŸãããã
ãã³ã¬ãŒã¿ã«æ
£ããŠããªãå ŽåïŒ
@connectorã»ã¯ã·ã§ã³ïŒãæåã®æ¹æ³ã¯ããããã³ã³ããŒãã³ãã®mixinãšåãã§ãããšç解ããããšã§ãã ãããããPythonãªã©ã®ä»ã®èšèªã§ãåæ§ã®æ§æäœãèŠãããšãããã§ãããã
ããã§ãªãå Žåãjavascriptã§ã¯ããããã¯äœããã®æ¹æ³ã§ä»ã®é¢æ°ïŒããã§ã¯ãã¯ã©ã¹ãïŒãå€æŽããåãªãé¢æ°ã§ãã
@connectãã³ã¬ãŒã¿ã¯ãå¥ã®ã³ã³ããŒãã³ãïŒ
<Connector>
ïŒã§ã¯ã©ã¹ãã©ããããã³ã³ããŒãã³ãã®ããããã£ãšããŠç¶æ
ã®èŠæ±ãããéšåã«ã¢ã¯ã»ã¹ã§ããããã
todos
ã䜿çšã§ããŸãã ãŸããReduxãã
dispatch
é¢æ°ãžã®ã¢ã¯ã»ã¹ãæäŸããŸããããã«ããã次ã®ããã«ã¢ã¯ã·ã§ã³ãåŠçã§ããŸãã
dispatch(actionCreator());
æåŸã«ãReduxã®
bindActionCreators
é¢æ°ã䜿çšããŠãé¢é£ããã¢ã¯ã·ã§ã³ã¯ãªãšãŒã¿ãŒã転éããŸãã
ã€ãŸããåã³ã³ããŒãã³ãã§ã¯ãã¢ã¯ã·ã§ã³äœæè
ã
dispatch()
é¢æ°ã§ã©ããããããšãªãçŽæ¥åŒã³åºãããšãã§ããŸãã
èŠãŠ
ã³ã³ããŒãã³ã/ TodosView.jsx import React from 'react'; export default class TodosView extends React.Component { handleDelete = (e) => { const id = Number(e.target.dataset.id);
ããã§ã¯ãã¢ã¯ã·ã§ã³äœæè
ã«é¢é£ä»ããããŠããåé€ãã¿ã³ãšå€æŽãã¿ã³ã®é£ã«ããªããžããªå
ã®åtodoèŠçŽ ã衚瀺ããŸãã
ãŸããã¯ã©ã¹ã®å®çŸ©ã§ã¯ãç¢å°ãé¢æ°ã䜿çšããŸãããã®ã³ã³ããã¹ãã¯ã¯ã©ã¹ã®ã³ã³ã¹ãã©ã¯ã¿ãŒã«é¢é£ä»ããããŠããŸãïŒãããã®é¢æ°ã¯ãšã°ãŒãã¥ãŒã¿ãŒããã³ã³ããã¹ããç¶æ¿ããããïŒã ES6ã¯ã©ã¹ã®éåžžã®é¢æ°ïŒã¬ã³ããŒãªã©ïŒã䜿çšããå Žåã¯ãããããã³ã³ããã¹ãã«é¢é£ä»ããå¿
èŠããããŸãã
React.createClassã䜿çšããŠåé¡ãåé¿ããããã¯ã¹ã€ã³ã䜿çšããããšãã§ããŸãããã¯ãªãŒã³ããšäžè²«æ§ã®ããã«ES6ã¯ã©ã¹ã䜿çšããããšã奜ã¿ãŸãã
æåŸã«ãå®çŸ©ããŸã
ã³ã³ããŒãã³ã/ TodosForm.jsx import React from 'react'; export default class TodosForm extends React.Component { handleSubmit = () => { let node = this.refs['todo-input'].getDOMNode(); this.props.createTodo(node.value); node.value = ''; } render() { return ( <div id="todo-form"> <input type="text" placeholder="type todo" ref="todo-input" /> <input type="submit" value="OK!" onClick={this.handleSubmit} /> </div> ); } }
ããã¯ããªããžããªã«todoãè¿œå ããã ãã®ããã ãã³ã³ããŒãã³ãã§ããããŸãã
次ã«ãã«ãŒãã決å®ããå¿
èŠããããŸã
shared / routes.jsx import Home from 'components/Home'; export default ( <Route name="app" component={App} path="/"> <Route component={Home} path="home" /> </Route> );

ãããŠã
httpïŒ// localhostïŒ8080 / homeã«ç§»åããŠãåäœäžã®ã¢ããªã±ãŒã·ã§ã³ãèŠãŠãã ãã
æåŸã®ããã³ãã£ã¢ïŒéåæã¢ã¯ã·ã§ã³
ç§ã¯ããªããèããŠããããšãç¥ã£ãŠããŸãã
ããã¯äžå¯èœã§ãã
ãããŠãç§ã¯ãããå¯èœã§ãããšèšããŸãïŒ
Reduxã®ãã1ã€ã®åªããæ©èœã¯ããã£ã¹ãããã£ããããã«ãŠã§ã¢ãå€å¥ããããšã§ããããã«ãããã¢ã¯ã·ã§ã³ãïŒéåæã§ïŒå€æŽã§ããŸãã Reduxã¹ã¬ãããããç¹å®ã®ã·ã°ããã£ãæã€é¢æ°ã§æ©èœããããšã«æ°ã¥ãããšæããŸãã
Reduxããã«ãŠã§ã¢ã䜿çšããŠãã¢ããªã±ãŒã·ã§ã³ã§ã®ã¢ã¯ã·ã§ã³ãç°¡åã«ããã¢ã¯ã·ã§ã³ã¯ãªãšãŒã¿ãŒãåæãããŸããããã«ãããçŸãããŠçŽ æŽãããES6ã®çŽæã䜿çšã§ããããã«ãªããŸãã
å
±æ/ lib / promiseMiddleware.js export default function promiseMiddleware() { return next => action => { const { promise, type, ...rest } = action; if (!promise) return next(action); const SUCCESS = type; const REQUEST = type + '_REQUEST'; const FAILURE = type + '_FAILURE'; next({ ...rest, type: REQUEST }); return promise .then(res = > { next({ ...rest, res, type: SUCCESS }); return true; }) .catch(error => { next({ ...rest, error, type: FAILURE });
ã€ãŸããã¢ã¯ã·ã§ã³ã®ãçŽæãããŒãå®çŸ©ããã ãã§ãèªåçã«è§£æ±ºæžã¿ãŸãã¯æåŠæžã¿ã®ç¶æ
ã«ãªããŸãã
ç¶æ
ã®å€åã远跡ããå¿
èŠãããå Žåã¯ããªãã·ã§ã³ã§èªåçæããã
<TYPE> _REQUESTããã³
<TYPE> _FAILUREã®ã¬ãã¥ãŒãµãŒã远跡ããããšãã§ããŸãã
ãããŠããããã䜿çšããã«ã¯ã
client / index.jsxããã³
server.jsxã®æ°è¡ãå€æŽããå¿
èŠããã
ãŸã ... import { applyMiddleware } from 'redux'; import promiseMiddleware from 'lib/promiseMiddleware'; ... const store = applyMiddleware(promiseMiddleware)(createStore)(reducer);
ãŸãã
ãªãã¥ãŒãµãŒãšãšãã«
initialStateã転éããããšãå¿ããªãã§ãã ãã
ãããŠä»ãç§ãã¡ã¯ã¢ã¯ã·ã§ã³äœæè
ã®ããã«
createTodoããžãã¯ã¢ã¯ã·ã§ã³ãæžãããšãã§ããŸããäŸãã°
import request from 'axios'; const BACKEND_URL = 'https://webtask.it.auth0.com/api/run/wt-milomord-gmail_com-0/redux-tutorial-backend?webtask_no_cache=1'; export function createTodo(text) { return { type: 'CREATE_TODO', promise: request.post(BACKEND_URL, { text }) } }
æžéæ©ã«ããããªå€æŽãå ããåŸã
return state.concat(action.res.data.text);

ããã§ãTodoãå€éšããŒã¿ããŒã¹ã«ä¿åãããŸããã ã¢ããªã±ãŒã·ã§ã³ã®éå§æã«ããããããŒãããå Žåã¯ãã¢ã¯ã·ã§ã³ã®
getTodosäœæè
ãè¿œå ããã ãã§ãã
export function getTodos() { return { type: 'GET_TODOS', promise: request.get(BACKEND_URL) } }
æžéæ©ã§åœŒãæãŸããŸã
case 'GET_TODOS': return state.concat(action.res.data);
ãããŠã
TodosViewãåæåããããšãã«åŒã³åºãããšãã§ããŸã
componentDidMount() { this.props.getTodos(); }
ããã«ãŠã§ã¢ãåæãªã¯ãšã¹ããŸãã¯å€±æã®å¯èœæ§ã«å¯ŸããŠã¢ã¯ã·ã§ã³ãåŒã³åºãå Žåãããããã¹ãŠããªãã¥ãŒãµãŒã§ãã£ããããããããããŒãæãŸãã¯ãšã©ãŒæã«ã¢ããªã±ãŒã·ã§ã³ã®ç¶æ
ãæŽæ°ããæ¹æ³ã確èªã§ããŸãã
åŸ
ã£ãŠ...ãããŠãæã
ã¯ç¶æ
ã®æ°Žåè£çµŠãç Žããªãã£ãã®ã§ããïŒ
ã¯ã ãããä¿®æ£ããŸãããïŒ
åé¡ã¯ãéåæã¢ã¯ã·ã§ã³ãè¿œå ããããç¶æ
ãã¯ã©ã€ã¢ã³ãã«éä¿¡ãããåã«éåæã¢ã¯ã·ã§ã³ãå®äºããã®ãåŸ
ããªãããšã§ãã ã¯ã©ã€ã¢ã³ãã«ããŒãç»é¢ã衚瀺ã§ããã®ã§ãããã¯éèŠã§ã¯ãªããšæããããããŸããã ãµãŒããŒã«ã¶ãäžããããã¯ãŸãã§ããïŒ
ãŸããããã¯å€ãã®èŠå ã«äŸåããŸãã ãµãŒããŒã¬ã³ããªã³ã°ã®äž»ãªå©ç¹ã¯ãããã¯ãšã³ãïŒåãããŒã¿ã»ã³ã¿ãŒã«ããå¯èœæ§ããããŸãïŒïŒãšã®è¯å¥œãªæ¥ç¶ãä¿èšŒã§ããããšã§ãã ããšãã°ããŠãŒã¶ãŒã倱ãããã¢ãã€ã«æ¥ç¶ãä»ããŠãµã€ããããŠã³ããŒãããããšããå ŽåããµãŒããŒãèªåã®ããŸããŸãªãªãœãŒã¹ããååŸãããããããµãŒããŒãåæç¶æ
ãååŸããã®ãåŸ
ã€æ¹ãã¯ããã«åªããŠããŸãã
çŸåšã®ç¶æ³ã§ãã®åé¡ã解決ããããšã¯ããã»ã©é£ãããããŸããã ããã¯ããã€ãã®æ¹æ³ã§è¡ãããšãã§ããŸãããç§ãããéã¯çæ³ããã¯ã»ã©é ãã§ãïŒ
ã¢ã¯ã·ã§ã³ã¯ãªãšãŒã¿ãŒã®é
åãšããŠãã³ã³ããŒãã³ãã«å¿
èŠãªããŒã¿ã決å®ããŸãã ã¯ã©ã¹å®çŸ©ã§éçããããã£ã䜿çšã§ããŸãã
static needs = [ TodoActions.getTodos ]
ãã¹ãŠã®promiseåŒã³åºãããã£ããããããŒã¿ãåéããŠéä¿¡ããé¢æ°ãå¿
èŠã§ã
shared / lib / fetchComponentData.js export default function fetchComponentData(dispatch, components, params) { const needs = components.reduce( (prev, current) => { return (current.needs || []) .concat((current.WrappedComponent ? current.WrappedComponent.needs : []) || []) .concat(prev); }, []); const promises = needs.map(need => dispatch(need(params))); return Promise.all(promises); }
åè¿°ã®ã¹ããŒãã³ã³ããŒãã³ãã¯
ã³ãã¯ã¿ã³ã³ããŒãã³ãã«ã©ãããããããã
WrappedComponentããŒã確èªããå¿
èŠãããããšã«æ³šæããŠãã ããã
次ã«ããã¹ãŠã®ããŒã¿ãæã£ããšãã«ã®ã¿å¿çããããã«ãµãŒããŒãæ§æããŸãã
import fetchComponentData from 'lib/fetchComponentData'; Router.run(routes, location, (err, routeState) => { if (err) return console.error(err); function renderView() {

åŒã³åºããç¹°ãè¿ãããã®ãé¿ããããã«ã
onComponentMountããã¢ã¯ã·ã§ã³äœæè
ãåé€ãã
npm run dev
ãåèµ·åããŠãµãŒããŒäžã®å€æŽãæŽæ°ããŠãã ããã
äœãåŠã³ãŸãããïŒ
ãªããŠçŸããäžçïŒ
ããã«å€ãã®ããšãã§ããŸãã å€æ°ã®ã«ãŒããããã¢ããªã±ãŒã·ã§ã³ã§ã¯ãããããReact Router
onLeaveãã³ãã©ãŒã䜿çšããŠãã³ã³ããŒãã³ããå¿
èŠãšãããã¹ãŠã®ããŒã¿ïŒ
ãããªã© ïŒãããŒãããéåæAPIã§ä»ã®ã¢ã¯ã·ã§ã³ããã£ããããŸãã
ããããã¹ãŠã«ãããããããçŽ æŽãããæ©èœçãªæªæ¥ãžã®å£®å€§ãªæ¢æ±ã楜ããã§ããã ããã°å¹žãã§ãã
Githubã§æçµçµæã確èªãã
ãã㧠Reduxã®è©³çŽ°ãèªãããšãã§ã
ãŸãã