
劻ã®ç®ãéããŠå圢ã¢ããªã±ãŒã·ã§ã³ãéçºãã
ããã¯ã React.jsã§å圢ã¢ããªã±ãŒã·ã§ã³ããŒãããéçºããããšã«é¢ããèšäºã®ç¶ãã§ãã ãã®ããŒãã§ã¯ãããã€ãã®ããŒãžã ããŒãã¹ãã©ãã ãã«ãŒãã£ã³ã°ã Fluxã³ã³ã»ãããããã³Reduxã®äžè¬çãªå®è£
ã远å ããŸãã
ç®æ¬¡
1ïŒ ååã¢ããªã±ãŒã·ã§ã³ã®åºæ¬ã¹ã¿ãã¯ãæ§ç¯ãã
2ïŒã«ãŒãã£ã³ã°ãšããŒãã¹ãã©ããã䜿çšããç°¡åãªã¢ããªã±ãŒã·ã§ã³ãäœæããŸã
3ïŒ APIãšèªèšŒãšã®çžäºäœçšãå®è£
ããŸã
ãã®ãããæåã®éšåã§ã¯ãåçŽãªHelloWorldã³ã³ããŒãã³ããéçºããã³ãŒãåè³ªãæ§ç¯ããã³ç£èŠããããã®ç°å¢ãæ§ç¯ããããšã«ãªããŸããã æ¬æ ŒçãªWebãµã€ããäœæãããšãã§ããã€ãŸããããã«ããã€ãã®ããŒãžã远å ãããããããªã³ã¯ããå圢ã«ãŒãã£ã³ã°ãå®è£
ããŸãã
1. react-bootstrapããããžã§ã¯ãã«è¿œå ããŸã
ããã¯ã Reactã¹ã¿ã€ã«ã®ããŒãã¹ãã©ããèŠçŽ ã䜿çšã§ããéåžžã«äººæ°ã®ããã©ã€ãã©ãªã§ãã
ããšãã°ããã¥ãŒæ§é ã®ä»£ããã«
<div className="nav navbar">
ç§ãã¡ã¯æžãããšãã§ããŸã
<Nav navbar>
ãŸãããªãªãžãã«ã®bootstrapã® JavaScriptã³ãŒãã¯ã react-bootstrapã³ã³ããŒãã³ãã«æ¢ã«å®è£
ãããŠããããã䜿çšããå¿
èŠã¯ãããŸããã
react-bootstrapãã€ã³ã¹ããŒã«ãã
npm i --save react-bootstrap
ãããžã§ã¯ãã«å€æŽãå ããŸã
HelloWorldãŠã£ãžã§ãããApp.jsxããåå¥ã®ã³ã³ããŒãã³ãã«åé¢ããŸãã App.jsxã¯ã¢ããªã±ãŒã·ã§ã³ã®å圢éšåãžã®ãšã³ããªãã€ã³ãã§ãããããã«ã¬ã€ã¢ãŠããšããŠæžãçŽãããã®äžã«ãŠãŒã¶ãŒãèŠæ±ããããŒãžã衚瀺ãããããšãæãåºããŸãã
ãªãã¡ã¯ã¿ãªã³ã°
- src / components / HelloWorldPageãã©ã«ããŒãäœæããŸã
- App.jsxã®ååãHelloWorldPage.jsxã«å€æŽãã App.cssã®ååãHelloWorldPage.cssã«å€æŽããŸã
- HelloWorldPage.jsxããã³HelloWorldPage.cssãã¡ã€ã«ãsrc / components / HelloWorldPageãã©ã«ããŒã«ç§»åããŸã
mkdir src/components/HelloWorldPage mv src/components/App.jsx src/components/HelloWorldPage/HelloWorldPage.jsx mv src/components/App.css src/components/HelloWorldPage/HelloWorldPage.css
- HelloWorldPage.jsxã«å€æŽãå ããŸã
--- import './App.css'; +++ import './HelloWorldPage.css';
- 次ã®å
容ã®index.jsãã¡ã€ã«ãäœæããŸã
src / components / HelloWorldPage / index.js
import HelloWorldPage from './HelloWorldPage'; export default HelloWorldPage;
ãã®æé ã«ãããã³ã³ããŒãã³ããã€ã³ããŒãã§ããããã«ãªããŸãã
import HelloWorldPage from 'components/HelloWorldPage';
ã®ä»£ããã«
import HelloWorldPage from 'components/HelloWorldPage/HelloWorldPage';
ããã¯ããæ£ç¢ºã§ãã¢ããªã±ãŒã·ã§ã³ã®ãœãŒã¹ã³ãŒãã®ã¡ã³ããã³ã¹ãç°¡çŽ åããŸãã
App.jsxãäœæãã
- ã¢ããªãã©ã«ããŒãäœæãã
- Appãã©ã«ããŒã«ã index.jsãšApp.jsxã® 2ã€ã®ãã¡ã€ã«ãäœæããŸã
mkdir src/components/App
src / components / App / index.js
import App from './App'; export default App;
src / components / App / App.jsx
import React, { Component } from 'react'; import Grid from 'react-bootstrap/lib/Grid'; import Nav from 'react-bootstrap/lib/Nav'; import Navbar from 'react-bootstrap/lib/Navbar'; import NavItem from 'react-bootstrap/lib/NavItem'; import HelloWorldPage from 'components/HelloWorldPage'; class App extends Component { render() { return ( <div> <Navbar> <Navbar.Header> <Navbar.Brand> <span>Hello World</span> </Navbar.Brand> <Navbar.Toggle /> </Navbar.Header> <Navbar.Collapse> <Nav navbar> <NavItem></NavItem> <NavItem></NavItem> </Nav> </Navbar.Collapse> </Navbar> <Grid> <HelloWorldPage /> </Grid> </div> ); } } export default App;
éèŠãªæ³šæïŒã©ã®react-bootstrapã³ã³ããŒãã³ããã€ã³ããŒããããæç€ºçã«è¿°ã¹ãŠããããšã«æ³šæããŠãã ããã ããã«ããããã«ãããã»ã¹ã§webpackããããžã§ã¯ãã§äœ¿çšãããreact-bootstrapéšåã®ã¿ãå«ããã©ã€ãã©ãªå
šäœã§ã¯ãªããç§ãæžããå Žåã®ããã«ãªããŸãã
import { Grid, Nav, Navbar, NavItem } from 'react-bootstrap';
ãã®æäœã¯ã䜿çšããã©ã€ãã©ãªãã¢ãžã¥ãŒã«æ¹åŒããµããŒãããŠããå Žåã«ã®ã¿æ©èœããããšã«æ³šæããããšãéèŠã§ãã ããšãã°ã react-bootstrapãšlodashã¯ãããã«å±ããŸããã jqueryãšmomentjsã¯å±ããŸããã
ãã®ã³ã³ããŒãã³ãã¯ããé©åã«å®è£
ã§ããŸããã³ãŒããããããããã«ãäžèšã®ã³ã³ããŒãã³ãã¯ç¶æ
ã§åäœããã ã³ã³ããŒãã³ãã¯ãŒã¯ãããŒã³ãŒã«ããã¯ ïŒ componentWillMountãcomponentDidMountãªã© ïŒã䜿çšããŸããã ããã¯ãããããPure Sateless Function ComponentãšããŠæžãæããããããšãæå³ããŸã ã
å°æ¥çã«ã¯ããã®æ¹æ³ã§èšè¿°ãããã³ã³ããŒãã³ãã«ã¯ããã©ãŒãã³ã¹äžã®å©ç¹ããããŸãïŒé¢æ°åããã°ã©ãã³ã°ã®çè«ãšçŽç²ãªé¢æ°ã®æŠå¿µã®ãããã§ãïŒãåã³ã³ããŒãã³ãã®çç£æ§ãé«ãã»ã©ãã¢ããªã±ãŒã·ã§ã³ã®çç£æ§ã¯åäžããŸãã
ãããŸã§ã®éã詊è¬ã¯ãããã®ã³ã³ããŒãã³ããéåžžã®ES6ã¯ã©ã¹ã«ã©ããããŸããã1ã€ã®çŽ æŽãããããŒãã¹ããããŸãã
ããã©ã«ãã§ã¯ãæ°ããå°éå
·ãç¶æ
å€ã以åã®ãã®ãšå®å
šã«äžèŽããå Žåã§ããæ°ããå°éå
·ãç¶æ
ã®å€ãåä¿¡ããããšãã³ã³ããŒãã³ãã¯åžžã«æŽæ°ãããŸãã ããã¯å¿
ãããå¿
èŠã§ã¯ãããŸããã éçºè
ã«ã¯ã trueãŸãã¯falseãè¿ãshouldComponentUpdateïŒnextPropsãnextStateïŒã¡ãœãããç¬ç«ããŠå®è£
ããæ©äŒããããŸãã ããã䜿çšãããšãã³ã³ããŒãã³ããåæç»ããå Žåãšããªãå Žåã«ãReactã«æç€ºçã«æç€ºã§ããŸãã
ã³ã³ããŒãã³ããPure Stateless Function ComponentãšããŠå®è£
ãããŠããå ŽåãReactèªäœã¯ã shouldComponentUpdateã®æç€ºçãªå®è£
ãªãã§ã³ã³ããŒãã³ãã®å€èŠ³ãæŽæ°ããå¿
èŠããããã©ããã倿ã§ããŸãã ã€ãŸã ãå°ãªãåŽåã§å€§ããªå©çãåŸãããšãã§ããŸãã
泚ïŒä»¥äžã®ã³ãŒãã¯ããã®ãããªã³ã³ããŒãã³ãã®ãã¬ãŒãã³ã°äŸã§ãã å°æ¥çã«ã¯App.jsxã«å€æŽãå ã ã çŽç²ãªã¹ããŒãã¬ã¹ã³ã³ããŒãã³ãã§ã¯ãªããªãããããã®äŸããããžã§ã¯ãã«è»¢éããªãã§ãã ããã
泚2ïŒãã®ãããžã§ã¯ãã§ã¯ãèšäºã®å
容ãè€éã«ãªããªãããã«ã çŽç²ãªã¹ããŒãã¬ã¹é¢æ°ã³ã³ããŒãã³ãã®åœ¢åŒã§å®è£
ããããšãå¯èœã§ãããæ£ããå Žåã§ãããã¹ãŠã®ã³ã³ããŒãã³ããES6ã¯ã©ã¹ã®åœ¢åŒã§å®è£
ããŸãã
import React from 'react'; import Grid from 'react-bootstrap/lib/Grid'; import Nav from 'react-bootstrap/lib/Nav'; import Navbar from 'react-bootstrap/lib/Navbar'; import NavItem from 'react-bootstrap/lib/NavItem'; import HelloWorldPage from './HelloWorldPage'; function App() { return ( <div> <Navbar> <Navbar.Header> <Navbar.Brand> <span>Hello World</span> </Navbar.Brand> <Navbar.Toggle /> </Navbar.Header> <Navbar.Collapse> <Nav navbar> <NavItem></NavItem> <NavItem></NavItem> </Nav> </Navbar.Collapse> </Navbar> <Grid> <HelloWorldPage /> </Grid> </div> ); } export default App;
ãã©ãŠã¶ãŒã§äœãå€ãã£ãã®ããèŠãŠã¿ãŸãããã ãããŠ...ã¯ãã ããŒãã¹ãã©ããã«ã¯ã¹ã¿ã€ã«ããããŸããã ç¬èªã®ããŒãã䜿çšããããã react-bootstrapéçºè
ã¯æå³çã«ãã£ã¹ããªãã¥ãŒã·ã§ã³ã«ããããå«ããŸããã§ããã ãããã£ãŠã bootstrap.comãªã©ã®bootstrapã®ããŒãããããµã€ãã«ã¢ã¯ã»ã¹ããŠã奜ããªãµã€ããããŠã³ããŒãããŸãã src / components / App / bootstrap.cssã«ä¿åããŸãã ã«ã¹ã¿ãã€ãºãç°¡åãªã®ã§ããã«ããŒãžã§ã³ãä¿æããããšããå§ãããŸãã ãããããšããšã«ããwebpackã«ãã£ãŠçž®å°ãè¡ãããŸã ã
æ³šïŒ githubã®ãªããžããªããããŒããããŠã³ããŒãã§ããŸãã
App.jsxã«å€æŽãå ããŸã
src / components / App / App.jsx
+++ import './bootstrap.css';
ç¹ã«ã°ãªãã³ã³ããããžã§ã¯ãã§äœ¿çšããªãããã ã°ãªãã£ã³ã³ã®äœæ¥ã®ã»ããã¢ããã«éäžããããªãã®ã§ã ã°ãªãã£ã³ã³ãã¹ã¿ã€ã«ããåé€ããŸãã
src / components / App / bootstrap.css
--- @font-face { --- font-family: 'Glyphicons Halflings'; --- src: url('../fonts/glyphicons-halflings-regular.eot'); src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); --- }
ãã©ãŠã¶ã«æ»ããšããã¹ãŠãæ£åžžã«è¡šç€ºãããã¯ãã§ãã
泚ïŒããŒãžããªããŒããããšãã«ãå€ãããŒãžã§ã³ã®ããŒãžãæåã«è¡šç€ºãããæ°ç§åŸã«æ°ããããŒãžã§ã³ã衚瀺ãããã®ã§ã nodemonãåèµ·åããŠãã ãã ã
2.ããã€ãã®ããŒãžãšã«ãŒãã£ã³ã°ã远å ããŸãã
2.1ã 2ã€ã®ã¹ã¿ããäœããŸããã
- src / components / CounterPageããã³src / components / TimePageãã©ã«ããŒãäœæããŸã
- ã¹ã¿ãã®ã³ãŒããæžã
src / components / CounterPage / index.js
import CounterPage from './CounterPage'; export default CounterPage;
src / components / CounterPage / CounterPage.jsx
import React, { Component } from 'react'; class CounterPage extends Component { render() { return <div> </div>; } } export default CounterPage;
src / components / TimePage / index.js
import TimePage from './TimePage'; export default TimePage;
src / components / TimePage / TimePage.jsx
import React, { Component } from 'react'; class TimePage extends Component { render() { return <div> </div>; } } export default TimePage;
2.2ã ã«ãŒãã£ã³ã°ã远å
ã«ãŒãã£ã³ã°ã«ã¯ã react-routerã©ã€ãã©ãªã䜿çšããŸã ã
npm i --save react-router
åäœãããã«ã¯ããããžã§ã¯ãã«æ¬¡ã®å€æŽãå ããå¿
èŠããããŸãã
- routesã§ãã¡ã€ã«ãå®çŸ©ããŸãã ãã®äžã§ã URLãšã¬ã³ããªã³ã°ãããã¹ãã³ã³ããŒãã³ããšã®å¯Ÿå¿ã瀺ããŸãã
- ã¢ããªã±ãŒã·ã§ã³ã®ãµãŒããŒåŽã§ã ãšã¯ã¹ãã¬ã¹ WebãµãŒããŒã¯èŠæ±URLãreact-routerããäžèŽé¢æ°ã«æž¡ããŸã ã renderPropsãè¿ããŸã ãããã䜿çšããŠããŠãŒã¶ãŒãèŠæ±ããã³ã³ãã³ããã¬ã³ããªã³ã°ããããäžèŽããªãããšãå ±åããŠããã404ãšã©ãŒã®ããŒãžãè¿ããŸãã
- ãŸãã react-routerã©ã€ãã©ãªãURLã®å€æŽã远跡ã§ããããã«ãã¢ããªã±ãŒã·ã§ã³ã®ã¯ã©ã€ã¢ã³ãéšåã«å€æŽãå ããŸãã æ°ããURLãèšå®ããããã¹ã®1ã€ãšäžèŽããå Žåãã¯ã©ã€ã¢ã³ãåŽã®JavaScriptã¯ãµãŒããŒã«ã¢ã¯ã»ã¹ããã«ããŒãžã³ã³ãã³ããæŽæ°ããŸãã æ°ããURLãèšå®ããããã¹ã®ããããšãäžèŽããªãå Žåããã©ãŠã¶ã¯ã¯ã©ã·ãã¯ãªã³ã¯ãã¯ãªãã¯ããŸãã
2.2.1ã ã«ãŒããã¡ã€ã«
src / routes.jsx
import React from 'react'; import { IndexRoute, Route } from 'react-router'; import App from 'components/App'; import CounterPage from 'components/CounterPage'; import HelloWorldPage from 'components/HelloWorldPage'; import TimePage from 'components/TimePage'; export default ( <Route component={App} path='/'> <IndexRoute component={HelloWorldPage} /> <Route component={CounterPage} path='counters' /> <Route component={TimePage} path='time' /> </Route> );
å®éãReactã³ã³ããŒãã³ãããšã¯ã¹ããŒãããŠããããšã«æ³šæããŠãã ããã IndexRouteã¯ãwebäžã®index.htmlãŸãã¯index.phpã«é¡äŒŒããŠããŸãããã¹ã®äžéšãçç¥ãããŠããå Žåã¯ãéžæãããŸãã
æ³šïŒ Routeããã³IndexRouteã³ã³ããŒãã³ãã¯ãä»ã®Routeã«äœåºŠã§ããã¹ãã§ããŸãã ãã®äŸã§ã¯ã2ã€ã®ã¬ãã«ã«å¶éããŠããŸãã
ãããã£ãŠã次ã®å¯Ÿå¿ã決å®ããŸãã
URL '/' =>ã¿ã€ã<HelloWorldPage />ã®ã³ã³ããŒãã³ã
URL '/ counter' => <CounterPage />
URL '/ time' => <TimePage />
ãã®ã¢ããªã±ãŒã·ã§ã³ã§ã¯ã Appã³ã³ããŒãã³ããã¬ã€ã¢ãŠãã®åœ¹å²ãæããå¿
èŠããããŸãããã®ãããåã蟌ã¿ã³ã³ããŒãã³ãïŒ å ïŒãã¬ã³ããªã³ã°ããããã«ããããæãããå¿
èŠããããŸãã
src / components / App / App.jsx
--- import React, { Component } from 'react'; +++ import React, { Component, PropTypes } from 'react'; import Grid from 'react-bootstrap/lib/Grid'; import Nav from 'react-bootstrap/lib/Nav'; import Navbar from 'react-bootstrap/lib/Navbar'; import NavItem from 'react-bootstrap/lib/NavItem'; --- import HelloWorldPage from 'components/HelloWorldPage'; import './bootstrap.css'; +++ const propTypes = { +++ children: PropTypes.node +++ }; class App extends Component { render() { return ( <div> <Navbar> <Navbar.Header> <Navbar.Brand> <span>Hello World</span> </Navbar.Brand> <Navbar.Toggle /> </Navbar.Header> <Navbar.Collapse> <Nav navbar> <NavItem></NavItem> <NavItem></NavItem> </Nav> </Navbar.Collapse> </Navbar> <Grid> +++ {this.props.children} --- <HelloWorldPage /> </Grid> </div> ); } } +++ App.propTypes = propTypes; export default App;
2.2.2ã¢ããªã±ãŒã·ã§ã³ã®ãµãŒããŒåŽã«ã«ãŒãã£ã³ã°ã远å ãã
src / server.js
--- import App from 'components/App'; +++ import { match, RouterContext } from 'react-router'; +++ import routes from './routes'; app.use((req, res) => { --- const componentHTML = ReactDom.renderToString(<App />); +++ match({ routes, location: req.url }, (error, redirectLocation, renderProps) => { +++ if (redirectLocation) { // redirect +++ return res.redirect(301, redirectLocation.pathname + redirectLocation.search); +++ } +++ if (error) { // +++ return res.status(500).send(error.message); +++ } +++ if (!renderProps) { // , URL +++ return res.status(404).send('Not found'); +++ } +++ const componentHTML = ReactDom.renderToString(<RouterContext {...renderProps} />); +++ return res.end(renderHTML(componentHTML)); +++ }); --- return res.end(renderHTML(componentHTML)); });
æ³šïŒ match颿°ã¯ãæåã®JavaScriptãã©ã¡ãŒã¿ãŒãšããŠã«ãŒããšãã±ãŒã·ã§ã³ããŒãæã€ãªããžã§ã¯ããåãå
¥ããŸãã ç§ã¯ã·ã§ãŒããã³ã衚èšES6ã䜿çšããŠããŸãããã«ããŒãžã§ã³ã¯æ¬¡ã®ããã«ãªããŸã
{ routes: routes, location: req.url},
ããã§ã routes.jsxãã¡ã€ã«ããã«ãŒããã€ã³ããŒãããŸãã 2çªç®ã®ãã©ã¡ãŒã¿ãŒãšããŠã matchã¯ã¬ã³ããªã³ã°ãè¡ãã³ãŒã«ããã¯é¢æ°ãåããŸãã
ã¯ã©ã€ã¢ã³ãåŽã®JavaScriptãäžæçã«ç¡å¹ã«ãã
src / client.js
ãã©ãŠã¶ã§ã«ãŒãã£ã³ã°ããã¹ãããŠã¿ãŸããããããŒãžã¯åãããã«èŠããŸããã Appã³ã³ããã«HelloWorldPageã³ã³ããŒãã³ããæç€ºçã«åã蟌ãå¿
èŠã¯ãããŸããã å
ã«é²ã¿ãŸãã
ä»ã®ããŒãžãžã®ãªã³ã¯ã远å ããŸãã éåžžãããã¯æ¬¡ã®ããã«è¡ãããŸãã
import { Link } from 'react-router'; <Link to='/my-fancy-path'>Link text</Link>
ãã ãã NavItemã³ã³ããŒãã³ãããªã³ã¯ãšããŠãªã³ã¯ããå¿
èŠããããŸãã ãããè¡ãã«ã¯ã react-router-bootstrapã©ã€ãã©ãªã䜿çšããŸãã
npm i --save react-router-bootstrap
src / components / App / App.jsx
+++ import { Link } from 'react-router'; +++ import LinkContainer from 'react-router-bootstrap/lib/LinkContainer'; --- <span>Hello World</span> +++ <Link to='/'>Hello World</Link> --- <NavItem></NavItem> +++ <LinkContainer to='/time'> +++ <NavItem></NavItem> +++ </LinkContainer> --- <NavItem></NavItem> +++ <LinkContainer to='/counters'> +++ <NavItem></NavItem> +++ </LinkContainer>
ãµãŒããŒã®ã«ãŒãã£ã³ã°ããã¹ãããŸãã
nodemonãåèµ·åããŸã ã ãã©ãŠã¶ãŒã§ãã éçºããŒã«ã ãã ãããã¯ãŒã¯ãã¿ããéããŸãã
ããã§ãäœæ¥ã®çµæãè©äŸ¡ããŠãããã²ãŒã·ã§ã³ã¡ãã¥ãŒã®ãªã³ã¯ãã¯ãªãã¯ã§ããŸãã èŠæ±ã¯ ã ãšã¯ã¹ãã¬ã¹ãåŠçããããµãŒããŒã«éãããããšã«æ³šæããŠãã ããã æ¬¡ã«ãèŠæ±ãããããŒãžã®HTMLã³ãŒããã¬ã³ããªã³ã°ããŠãã©ãŠã¶ãŒã«è¿ããŸãã ããã§ãã¢ããªã±ãŒã·ã§ã³ã¯åŸæ¥ã®Webã¢ããªã±ãŒã·ã§ã³ãšãŸã£ããåãããã«æ©èœããŸãã
ã¯ã©ã€ã¢ã³ãã®JavaScriptãããŒãããŠåæåããæéããªãå ŽåããŸãã¯ãšã©ãŒãçºçããå Žåãã¢ããªã±ãŒã·ã§ã³ã¯æ£åžžã«æ©èœããŸãã
2.2.3ã¢ããªã±ãŒã·ã§ã³ã®ã¯ã©ã€ã¢ã³ãéšåã«ã«ãŒãã£ã³ã°ã远å ããŸãã
src / client.js
--- import App from 'components/App'; +++ import { browserHistory, Router } from 'react-router'; +++ import routes from './routes'; ---
æ³šïŒ ã«ãŒã¿ãŒã³ã³ããŒãã³ããã¢ããªã±ãŒã·ã§ã³ã®ã«ãŒãã³ã³ããŒãã³ãã«ãªã£ãããšã«æ³šæããŠãã ããã URLã®å€æŽã远跡ããèšå®ããã«ãŒãã«åºã¥ããŠããŒãžã³ã³ãã³ããçæããŸãã
ãã©ãŠã¶ãŒã«æ»ãããªã³ã¯ãå床ã¯ãªãã¯ããŠã Developer ToolsããŒã«ã®Networkã¿ããæ³šææ·±ã芳å¯ããŸãã ä»åã¯ãããŒãžã¯ãªããŒããããããµãŒããŒãžã®ãªã¯ãšã¹ãã¯æ¶ãããã¯ã©ã€ã¢ã³ãåŽã®JavaScriptã¯ãªã¯ãšã¹ããããããŒãžãäœåºŠãã¬ã³ããªã³ã°ããŸãã ãã¹ãŠãæ©èœããŸãïŒ
äžéçµæ
è€æ°ã®ããŒãžã远å ããã¯ã©ã€ã¢ã³ããšãµãŒããŒã®ã«ãŒãã£ã³ã°ãæ£åžžã«æ§æããŠããã¹ãŠã®ã·ããªãªã§æ£ããæ©èœããããšã確èªããŸããã
3.ãã©ãã¯ã¹ãšãªããã¯ã¹
ãŸãã FluxãšReduxã«ã€ããŠè©±ãããšãã§ããéãç·Žç¿ã§ããããã«ããã«ãŠã³ã¿ãŒããå«ãããŒãžãå®è£
ããŸãã
Counter.jsxãšStateCounter.jsxã® 2ã€ã®æ°ããã³ã³ããŒãã³ããäœæããŸãããã
ã«ãŠã³ã¿ãŒã¯ãæž¡ãããå€ãšããã®å€ã®å€æŽãæ
åœãããã©ã¹ãã¿ã³ã衚瀺ããŸãã
StateCounter - Counterã³ã³ããŒãã³ãã®èŠªã³ã³ããŒãã³ãã çŸåšã®ã«ãŠã³ã¿ãŒå€ãç¬èªã®ç¶æ
ã¹ãã¬ãŒãžã«ä¿åãããã©ã¹ãã¿ã³ãã¯ãªãã¯ãããšãã«ãã®å€ãæŽæ°ããããã®ããžãã¹ããžãã¯ãå«ãŸããŸãã
ã€ã³ã¿ãŒãã§ã€ã¹ãšããžãã¹ããžãã¯ãæç€ºçã«åé¢ããããã«ããã®ãããªå®è£
ãæå³çã«éžæããŸããã
ãã®ãããªã³ãŒãã¯ç°¡åãªã®ã§ããã®ææ³ã¯å®éã«éåžžã«ãã䜿çšãããŸãã
- ãµããŒããšåè¡ãã
- ãã¹ããã
- åå©çšã
ç¹ã«ããã®ãããžã§ã¯ãã§ã¯ãè€æ°ã®ã³ã³ããŒãã³ããäžåºŠã«Counterã䜿çšããŸãã
src / components / CounterPage / Counter.jsx
import React, { Component, PropTypes } from 'react'; import Button from 'react-bootstrap/lib/Button'; import './Counter.css'; const propTypes = { onClick: PropTypes.func, value: PropTypes.number }; const defaultProps = { onClick: () => {}, value: 0 }; class Counter extends Component { render() { const { onClick, value } = this.props; return ( <div> <div className='counter-label'> Value: {value} </div> <Button onClick={onClick}>+</Button> </div> ); } } Counter.propTypes = propTypes; Counter.defaultProps = defaultProps; export default Counter;
src / components / CounterPage / Counter.css
.counter-label { display: inline-block; margin-right: 20px; }
src / components / CounterPage / StateCounter.jsx
import React, { Component } from 'react'; import Counter from './Counter'; class StateCounter extends Component { constructor() { super(); this.handleClick = this.handleClick.bind(this); this.state = { value: 0 }; } handleClick() { this.setState({ value: this.state.value + 1 }); } render() { return <Counter value={this.state.value} onClick={this.handleClick} />; } } export default StateCounter;
src / components / CounterPage / CounterPage.jsx
+++ import PageHeader from 'react-bootstrap/lib/PageHeader'; +++ import StateCounter from './StateCounter'; render() { --- return <div> </div>; +++ return ( +++ <div> +++ <PageHeader>Counters</PageHeader> +++ <h3>State Counter</h3> +++ <StateCounter /> +++ </div> +++ ); }
æŽæ°ãããã³ãŒãããã¹ãããŸãã ãã©ãŠã¶ã§ããã«ãŠã³ã¿ãã¿ãã«ç§»åããã+ããã¿ã³ãã¯ãªãã¯ããŸãã å€ã0ãã1ã«å€æŽãããŸããã æ¬¡ã«ãä»ã®ã¿ãã«ç§»åããŠããæ»ããŸãã ã«ãŠã³ã¿å€ã¯åã³ã0ãã«ãªããŸããã ããã¯éåžžã«æåŸ
ãããŠããŸãããåžžã«ç§ãã¡ãèŠãããšæããã®ã§ã¯ãããŸããã
Fluxã®æŠå¿µã«ã€ããŠèª¬æããŸãã

æ³šïŒ Fluxã¯æŠå¿µã§ãããã©ã€ãã©ãªã§ã¯ãããŸããã 仿¥ããããå®è£
ããå€ãã®ç°ãªãã©ã€ãã©ãªããããŸãã
3.1ã ãã©ãã¯ã¹ãã£ã³ã¬ãŒããã
ã³ã³ããŒãã³ãã«ã¯ããžãã¹ããžãã¯ã¯å«ãŸããŸããããã€ã³ã¿ãŒãã§ã€ã¹ã®ã¬ã³ããªã³ã°ã®ã¿ãæ
åœããŸãã
ã¢ããªã±ãŒã·ã§ã³ã«ã¯ãã¢ããªã±ãŒã·ã§ã³å
šäœã®ç¶æ
ãä¿åãããªããžã§ã¯ãã1ã€ãããŸãã ããããã°ããŒãã«ç¶æ
ããšåŒã³ãŸããããããæãæåããçšèªã§ãããã©ããã¯å®å
šã«ã¯ããããŸããã éçºè
ã®èŠæ±ã«ãããäžéšã®ã³ã³ããŒãã³ãã¯ãé¢å¿ã®ããã°ããŒãã«ç¶æ
ã®äžéšã«ããµãã¹ã¯ã©ã€ããããŸãã æéãçµã€ã«ã€ããŠãã°ããŒãã«ç¶æ
ã¯å€åããå¯èœæ§ããããããã«ãµãã¹ã¯ã©ã€ãããŠãããã¹ãŠã®ã³ã³ããŒãã³ãã¯èªåçã«æŽæ°ãåä¿¡ããŸãã
- ã³ã³ããŒãã³ãå
ã®ã°ããŒãã«ç¶æ
ãæç€ºçã«å€æŽããããšã¯çŠæ¢ãããŠããŸãã ã°ããŒãã«ç¶æ
ã倿Žããããã«ãã³ã³ããŒãã³ãã¯ç¹å¥ãªãã£ã¹ããã颿°ãåŒã³åºããŸãã ãã®é¢æ°ã®é²è¡ç¶æ³ã远跡ããããšã¯ãæåã®ãã©ãã¯ã¹ã®ååã«éåãããããã¢ã³ããã¿ãŒã³ã§ãã å®éã«ã¯ãã°ããŒãã«ç¶æ
ã«ã¯ãã³ã³ããŒãã³ãã«å¿
èŠãªãã¹ãŠã®æ
å ±ãããšãã°APIãªã¯ãšã¹ãã®å®è¡ã¹ããŒã¿ã¹ããšã©ãŒãå«ãŸããŸãã ãã®æ
å ±ããã³ãã®ä»ã®æ
å ±ã¯ã propsã䜿çšããŠã¿ã€ã ãªãŒãã€æç€ºçã«ã³ã³ããŒãã³ãã«éä¿¡ãããŸãã
éèŠãªæ³šæïŒã°ããŒãã«ç¶æ
ã¯ã ããã³ããšã³ãã¢ããªã±ãŒã·ã§ã³ã®ç¶æ
ã®ã¿ãå¥ã®ã¿ãã«èšè¿°ãããã©ãŠã¶ãŒã®RAMã«æä»çã«ä¿åãããŸãã ãããã£ãŠããŠãŒã¶ãŒãF5ãæŒããšå€±ãããŸãããããã¯èšèšäžäºæ³ãããããŸã£ããæ£åžžãªåäœã§ãã ãã®ãããã¯ã«ã€ããŠã¯ã第3éšã§è©³ãã説æããŸãã
å®çšäŸ
ãªã³ã©ã€ã³ã¹ãã¢ã®ãŠã§ããµã€ãããããšããŸãïŒããŒãžã®äžå€®ã«ãããã²ãŒã·ã§ã³ããã«ã«è£œåã®ãªã¹ãã衚瀺ãããŸã-補åã®æ°ãšãã®åèšå€ã衚瀺ããããã¹ã±ããã¢ã€ã³ã³ãå³åŽã®ã©ããã«-ãã¹ã±ããã«è¿œå ãããååã®è©³çްã衚瀺ãããããã㯠äžèšã§èšãã°ãããªãäžè¬çãªã·ããªãªã§ãã

ãŠãŒã¶ãŒèŠç¹ã·ããªãª
- ãŠãŒã¶ãŒã¯ãã«ãŒãã«è¿œå ããã¿ã³ãã¯ãªãã¯ããŸãã
- ãã¿ã³ãã¢ã¯ãã£ãã§ãªããªããã¢ã€ã³ã³ãããŒãã€ã³ãžã±ãŒã¿ã«å€ãããŸãã
- ãµãŒããŒAPIèŠæ±ãé²è¡äžã§ãã
- ãµãŒããŒããŒãããªã¯ãšã¹ããæ£åžžã«åŠçããŠåçãè¿ããåŸããã¢ã€ãã ãã«ãŒãã«æ£åžžã«è¿œå ãããŸããããšããããã³ãããäžéšã«è¡šç€ºãããè£œåæ°ã®ã¢ã€ã³ã³ã®å€ã1ã€å¢ããéé¡ãåèšç®ãããã«ãŒãã®å
容ã®è©³çްãå«ãæ°ããã¬ã³ãŒãããããã¯ã«è¡šç€ºãããŸãã ããŠã³ããŒãã€ã³ãžã±ãŒã¿ãæ¶ãããã¿ã³èªäœãåã³ã¢ã¯ãã£ãã«ãªããŸãã
- æé 3ã§ãšã©ãŒãçºçããå ŽåããŠãŒã¶ãŒã«ãšã©ãŒã¡ãã»ãŒãžã衚瀺ãããã¹ã±ããã«è£œåã远å ããããã®ãã¿ã³ãå
ã®ç¶æ
ã«æ»ããŸãã
ãã®ã¹ã¯ãªãããjQueryã§èšè¿°ããå ŽåãDOMãæäœããããã«å€ãã®ã³ãŒããèšè¿°ããå¿
èŠããããŸãã ãã¹ãŠã®æ°ãã顧客èŠä»¶ãå®è£
ããéçšã§ãã³ãŒãã¯ããè€éã«ãªããé«ã確çã§äœããæçµçã«æ
éãããµããŒãã®è€éããšã³ã¹ãã¯æéãšãšãã«æ°ããããŠã£ãã·ã¥ãªã¹ãããšãšãã«çµ¶ããå¢å ããŸãã
ãã©ãã¯ã¹ã®èгç¹ããåãã·ããªãª
泚ïŒã«ãŒãã«è¿œå ãéç¥ãã«ãŒããããã³ã«ãŒãã®è©³çްã³ã³ããŒãã³ãã¯ãã°ããŒãã«ç¶æ
ã«ãµãã¹ã¯ã©ã€ããããŸãã
- ãŠãŒã¶ãŒããã«ãŒãã«è¿œå ããã¿ã³ãã¯ãªãã¯ãããšã ãã£ã¹ããã颿°ãåŒã³åºãããŸãã
- ãã®é¢æ°ã¯ã°ããŒãã«ç¶æ
ãæŽæ°ããŸããAddto Cartãã¿ã³ã¯trueã«çããæ°ããããããã®
loading
å€ãåãåããããããªãã«ããã¢ã€ã³ã³ããã®ã³ã³ããŒãã³ãã®ãœãŒã¹ã³ãŒãã«åŸã£ãŠããŒãã€ã³ãžã±ãŒã¿ãŒã«å€ãããŸãã - 次ã®ã¹ãããã颿°ã¯APIã«ãªã¯ãšã¹ããè¡ãã远å ããã補åã«é¢ããæ
å ±ãããã¯ãšã³ãã«ä¿åããŸãã
- æåããå Žåãã°ããŒãã«ç¶æ
ãæŽæ°ããŸããéç¥ã³ã³ããŒãã³ãã¯æ°ããprop
message
å€ãåãåãããŠãŒã¶ãŒã«è¡šç€ºããŸããCartã³ã³ããŒãã³ãã¯æ°ããæ°éã®prop count
å€ãåãåããæ³šæéé¡ã®prop value
ãåãåããŸããCartDetailã³ã³ããŒãã³ãã¯å€ãåãåããŸãå°éå
·items
-ã«ãŒãã«è¿œå ããããã¹ãŠã®ã¢ã€ãã ã«äžèŽããã¢ã€ãã ã®æŽæ°ãªã¹ãã å°æ¥ãã客æ§ãããŒãžäžã§äœãä»ã®ããšããããå Žåãä»ã®ã³ã³ããŒãã³ãã®ã³ãŒããããžãã¹ããžãã¯ãå®è¡ããæ©èœã倿Žããããšãªããç°¡åã«å®çŸã§ããŸãã æ°ããã³ã³ããŒãã³ããå®è£
ããã ãã§ãããã«ã°ããŒãã«ç¶æ
ã®ã©ã®éšåã«é¢å¿ããããã瀺ãããšãã§ããŸãã - APIããšã©ãŒãè¿ããå Žåãéç¥ã³ã³ããŒãã³ãã¯å¯Ÿå¿ããprop
message
å€ãåä¿¡ããæ
å ±ã¡ãã»ãŒãžããŠãŒã¶ãŒã«è¡šç€ºããŸãã - ãã®é¢æ°ã¯ããã«ãŒãã«è¿œå ããã¿ã³ã«falseã«çãããããã
loading
æ°ããå€ãéç¥ããããšã«ãããæåŸã«ã°ããŒãã«ç¶æ
ãæŽæ°ããŸã ã ãã¿ã³ã¯å
ã®ç¶æ
ã«æ»ããŸãã
ãã®ãããªé¢æ°ã®ãµã³ãã«ã³ãŒã
function addItemToCart(itemId) { return (dispatch) => { dispatch(addItemToCartStarted(itemId)); addItemToCartAPICall(itemId) .then( (data) => { dispatch(itemToCardAdded(data)); dispatch(addItemToCartFinished(data)); } ) .catch(error => dispatch(addItemToCartFailed(itemId, error))); } }
åçŽåãããŠããŸãããå®å
šã«æ£ããããã§ã¯ãããŸããããã®äŸã§ã¯ã ãã£ã¹ããã颿°ãã°ããŒãã«ç¶æ
ã®æŽæ°ãæ
åœããŠããŸãã , - , dispatch .
.
- , -,
<Button onClick={() => dispatch(addItemToCart(3))} />
.
- , , props .
!
3.2ã Redux
: Flux .
é·æïŒ
- : , ;
- . . , - , , ;
- . 2 ;
- â , facebook .
çæïŒ
:
- ;
- - ;
- redux ;
- redux ;
- redux ;
- "", ;
- ;
- .
: : " ?" " ?". ! , . , . , - , ! , , , , !
3.2.1. redux , react-redux redux-thunk
npm i --save redux react-redux redux-thunk
3.2.2. -
3.2.2.1 src/redux , src/redux/actions src/redux/reducers .
3.2.2.2 counterActions.js . , .
src/redux/actions/counterActions.js
export const INCREMENT_COUNTER = 'INCREMENT_COUNTER'; export function incrementCounter() { return { type: INCREMENT_COUNTER }; }
3.2.2.3 counterReducer.js . .
src/redux/reducers/counterReducer.js
import { INCREMENT_COUNTER } from 'redux/actions/counterActions'; const initialState = { value: 0 }; export default function(state = initialState, action) { switch (action.type) { case INCREMENT_COUNTER: return { value: state.value + 1 }; default: return state; } }
ãã®ã³ãŒãã¯äœãããŸããïŒ
- , 0.
- INCREMENT_COUNTER , value 1.
- â .
: , redux ( "") @@INIT , .
3.2.3 redux
configureStore.js
import { applyMiddleware, combineReducers, createStore } from 'redux'; import thunk from 'redux-thunk'; import counterReducer from './reducers/counterReducer'; export default function (initialState = {}) { const rootReducer = combineReducers({ counter: counterReducer }); return createStore(rootReducer, initialState, applyMiddleware(thunk)); }
ãã®ã³ãŒãã¯äœãããŸããïŒ
- combineReducers , . , , -.
- . .
- thunk middleware . "" â , dispatch . , .
- createStore , "" .
3.2.4. redux
src/server.js
+++ import { Provider } from 'react-redux'; +++ import configureStore from './redux/configureStore'; app.use((req, res) => { +++ const store = configureStore(); ... --- const componentHTML = ReactDom.renderToString(<RouterContext {...renderProps} />); +++ const componentHTML = ReactDom.renderToString( +++ <Provider store={store}> +++ <RouterContext {...renderProps} /> +++ </Provider> +++ );
â props . props , child child . , , , , API , , .
, , . API , , , .
Provider react-redux store. , , , .
3.2.5. redux
src/client.js
+++ import { Provider } from 'react-redux'; +++ import configureStore from './redux/configureStore'; +++ const store = configureStore(); const component = ( +++ <Provider store={store}> <Router history={browserHistory}> {routes} </Router> +++ </Provider> );
3.2.6. "",
import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import Counter from './Counter'; import { incrementCounter } from 'redux/actions/counterActions'; const propTypes = { dispatch: PropTypes.func.isRequired, value: PropTypes.number.isRequired }; class ReduxCounter extends Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); } handleClick() { this.props.dispatch(incrementCounter()); } render() { return <Counter value={this.props.value} onClick={this.handleClick} />; } } ReduxCounter.propTypes = propTypes; function mapStateToProps(state) { const { value } = state.counter; return { value }; } export default connect(mapStateToProps)(ReduxCounter);
connect . :
- mapStateToProps , , "" , ;
- dispatch ;
- , mapStateToProps "" props ReduxCounter .
: connect High Order Components HOCs .
High Order ComponentsHigh Order Component : , , , , , props .
connect .
function connect(mapStateToProps) { function dispatch(...) { ... } const injectedProps = mapStateToProps(globalState); return (WrappedComponent) => { class HighOrderComponent extends Component { render() { <WrappedComponent {...this.props} {...injectedProps} dispatch={dispatch} />; } }; return HighOrderComponent; } }
, export default connect(mapStateToProps)(ReduxCounter) ?
- connect mapStateToProps .
- connect mapStateToProps , . { value: "_____"} , injectedProps .
- connect .
- ReduxCounter .
- props , ( {...this.props} ), injectedProps dispatch
3.2.7. CounterPage
src/components/CounterPage/CounterPage.jsx
+++ import ReduxCounter from './ReduxCounter'; <StateCounter /> +++ <h3>Redux Counter</h3> +++ <ReduxCounter />
3.2.8. ãã¹ãäž
- "+" . .
- , . , . ããïŒ åäœããŸãïŒ
, , !
github â https://github.com/yury-dymov/habr-app/tree/v2 .
- .
- API.
- API.
- react-bootstrap
- react-router
- Flux
- redux
Ps , , . äºåã«æè¬ããŸãïŒ