
ãå®éã®ããããžã§ã¯ãã§ã¯ããµãŒããŒãŸãã¯ãŠãŒã¶ãŒããããŒã¿ãåãåãããã©ãŒããããæ€èšŒãæ£èŠåãããã³ãã®ä»ã®æäœãå®è¡ããŸãã ãããã¯ãã¹ãŠããžãã¹ããžãã¯ãšèŠãªãããã¢ãã«ã«é
眮ããå¿
èŠããããŸãã åå¿ã¯ããŠãŒã¶ãŒã€ã³ã¿ãŒãã§ã€ã¹ãäœæããããã®M
V Cãã€ã®3åã®1ã«éããªããããããžãã¹ããžãã¯ã«ã¯å¥ã®ãã®ãå¿
èŠã§ãã äžéšã¯
reduxãŸãã¯
fluxãã¿ãŒã³ã䜿çšããäžéšã¯
Backbone.jsãŸãã¯ããã«è§åºŠã䜿çšãã
mobx.jsã
M odelãšããŠäœ¿çšããŸãã
åã®èšäºã§ãç§ãã¡ã¯ãã§ã«
åºç€ãæºåããŸããããããåºã«æ§ç¯ããŸãã mobxã¯ã¹ã¿ã³ãã¢ãã³ã©ã€ãã©ãªã§ãããããreactãšãªã³ã¯ããã«ã¯
mobx-reactãå¿
èŠ
ã§ã ã
npm i --save mobx mobx-react
ããã«ããã³ã¬ãŒã¿ãæäœããŠã¯ã©ã¹ããããã£ã倿ããã«ã¯ãbabelãã©ã°ã€ã³
babel-plugin-transform-class-propertiesããã³
babel-plugin-transform-decorators-legacyãå¿
èŠã§ãã
npm i --save-dev babel-plugin-transform-decorators-legacy babel-plugin-transform-class-properties
ãããã.babelrcã«è¿œå ããããšãå¿ããªãã§ãã ãã
"plugins": [ "react-hot-loader/babel", "transform-decorators-legacy", "transform-class-properties" ]
Menuã³ã³ããŒãã³ãããããŸããåŒãç¶ãäœæ¥ãç¶ããŸãããã ããã«ã«ã¯2ã€ã®ããªãŒãã³/ã¯ããŒãºãç¶æ
ããããmobxã䜿çšããŠç¶æ
ã管çããŸãã
1.æåã«ã@ observableãã³ã¬ãŒã¿ãŒã远å ããŠ
ãç¶æ
ã
å€å¥ãã芳枬å¯èœã«ããå¿
èŠããããŸãã ç¶æ
ã¯ããªããžã§ã¯ããé
åãã¯ã©ã¹ãªã©ã®ä»»æã®ããŒã¿æ§é ã§è¡šãããšãã§ããŸãã storesãã£ã¬ã¯ããªã«ã¡ãã¥ãŒã®ã¹ãã¢ïŒmenu-store.jsïŒãäœæããŸãã
import { observable} from 'mobx'; class MenuStore { @observable show; constructor() { this.show = false; } } export default new MenuStore();
Storeã¯ãåäžã®ã·ã§ãŒããããã£ãæã€ES6ã¯ã©ã¹ã§ãã @observableãã³ã¬ãŒã¿ãããã«æããŠãmobxã«ãããèŠãããã«äŒããŸããã Showã¯ã倿Žããããã«ã®ç¶æ
ã§ãã
2.
ç¶æ
ã®å€åã«å¯Ÿå¿ãããã¥ãŒãäœæããŸã ã æ¢ã«æã£ãŠããã®ã¯è¯ãããšã§ããã³ã³ããŒãã³ã/ã¡ãã¥ãŒ/index.jsã§ãã ããã§ãç¶æ
ãå€åãããšãã¡ãã¥ãŒãèªåçã«ãªãã€ã¬ã¯ããããŸãããmobxã¯ãã¥ãŒãæŽæ°ããæçã®æ¹æ³ãèŠã€ããŸãã ãã®ããã«ã¯ããªãã¶ãŒããŒã§åå¿ã³ã³ããŒãã³ããèšè¿°ãã颿°ãã©ããããå¿
èŠããããŸãã
ã³ã³ããŒãã³ã/ã¡ãã¥ãŒ/index.js import React from 'react'; import cn from 'classnames'; import { observer } from 'mobx-react'; import menuStore from '../../stores/menu-store'; import styles from './style.css'; const Menu = observer(() => ( <nav className={cn(styles.menu, { [styles.active]: menuStore.show })}> <div className={styles['toggle-btn']}>â°</div> </nav> )); export default Menu;
åå¿ã¢ããªã±ãŒã·ã§ã³ã§ã¯ãclassNameãæäœããããã«
classnamesãŠãŒãã£ãªãã£ãå¿
èŠã§ãã 以åã¯ãreactããã±ãŒãžã®äžéšã§ããããçŸåšã¯åå¥ã«ã€ã³ã¹ããŒã«ãããŸãã
npm i --save classnames
ãã®å©ããåããŠãããŸããŸãªæ¡ä»¶ã䜿çšããŠã¯ã©ã¹åãæ¥çããããšãã§ããŸããããã¯ããããã®ãªãããšã§ãã
ã¡ãã¥ãŒç¶æ
ã®å€ã=== trueã瀺ãå Žåãã¯ã©ã¹ãã¢ã¯ãã£ããã远å ããããšãããããŸãã ãã®ã³ã³ã¹ãã©ã¯ã¿ãŒã®ç¶æ
ãthis.show = trueã«å€æŽãããšãããã«ã«ã¯ãã¢ã¯ãã£ããªãã¯ã©ã¹ãå«ãŸããŸãã
3.
ç¶æ
ã倿Žããå¿
èŠããããŸã ã ã§ãã³ããŒã¬ãŒã®ã¯ãªãã¯ã€ãã³ãã远å ããŸã
ã¡ãã¥ãŒ/index.js <div onClick={() => { menuStore.toggleLeftPanel() }} className={styles['toggle-btn']}>â°</div>
ããã³toggleLeftPanelïŒïŒã¡ãœãã
ã¹ãã¢/ menu-store.js import { observable } from 'mobx'; class MenuStore { @observable show; constructor() { this.show = false; } toggleLeftPanel() { this.show = !this.show; } } const menuStore = new MenuStore(); export default menuStore; export { MenuStore };
泚ïŒããã©ã«ãã§ã¯ãã¹ãã¬ãŒãžãã·ã³ã°ã«ãã³ã€ã³ã¹ã¿ã³ã¹ãšããŠãšã¯ã¹ããŒãããŸããã¯ã©ã¹ã¯ããã¹ããªã©ã«ãå¿
èŠã«ãªãå ŽåããããããçŽæ¥ãšã¯ã¹ããŒããããŸããæç¢ºã«ããããã«ãã¹ã¿ã€ã«ã远å ããŸãã
ã³ã³ããŒãã³ã/ã¡ãã¥ãŒ/styles.css .menu { position: fixed; top: 0; left: -180px; bottom: 0; width: 220px; background-color: tomato; &.active { left: 0; } & .toggle-btn { position: absolute; top: 5px; right: 10px; font-size: 26px; font-weight: 500; color: white; cursor: pointer; } }
ãããŠãã¢ã€ã³ã³ãã¯ãªãã¯ããŠãããã«ãééããããšã確èªããŠãã ããã ããã«ã®ç¶æ
ãå¶åŸ¡ããããã«ãæå°éã®mobxã¹ãã¢ãäœæããŸããã èãäœããå¥ã®ã³ã³ããŒãã³ãããããã«ãå¶åŸ¡ããŠã¿ãŸãããã ããã«ãéãããéãããããã«ã¯ã远å ã®ã¡ãœãããå¿
èŠã§ãã
ã¹ãã¢/ menu-store.js import { observable, computed, action } from 'mobx'; class MenuStore { @observable show; constructor() { this.show = false; } @computed get isOpenLeftPanel() { return this.show; } @action('toggle left panel') toggleLeftPanel() { this.show = !this.show; } @action('show left panel') openLeftPanel() { this.show = true; } @action('hide left panel') closeLeftPanel() { this.show = false; } } const menuStore = new MenuStore(); export default menuStore; export { MenuStore };
èšç®ãã³ã¬ãŒã¿ãšã¢ã¯ã·ã§ã³ãã³ã¬ãŒã¿ã远å ãããŠããããšã«æ°ä»ããããããŸãããããããã¯å³å¯ã¢ãŒãïŒããã©ã«ãã§ã¯ç¡å¹ïŒã§ã®ã¿å¿
èŠã§ãã èšç®å€ã¯ã察å¿ããããŒã¿ã倿Žããããšèªåçã«åèšç®ãããŸãã ã¢ã¯ã·ã§ã³ã䜿çšããããšããå§ãããŸããããã«ãããã¢ããªã±ãŒã·ã§ã³ãããé©åã«æ§æããããã©ãŒãã³ã¹ãæé©åã§ããŸãã ã芧ã®ãšãããæåã®åŒæ°ã¯ã¢ã¯ã·ã§ã³ã®æ¡åŒµåãèšå®ããŸãã ããã§ããããã°æã«ãã©ã®ã¡ãœãããåŒã³åºãããç¶æ
ãã©ã®ããã«å€åãããã芳å¯ã§ããŸãã
泚ïŒéçºæã«ã¯ã mobxãšreact ãããã³react-mobx devtoolsã«Chromeæ¡åŒµæ©èœã䜿çšãããšäŸ¿å©ã§ãå¥ã®ã³ã³ããŒãã³ããäœæãã
ã³ã³ããŒãã³ã/å·Šããã«ã³ã³ãããŒã©ãŒ/ index.js import React from 'react'; import menuStore from '../../stores/menu-store'; import styles from './styles.css'; const Component = () => ( <div className={styles.container}> <button onClick={()=>{ menuStore.openLeftPanel(); }}>Open left panel</button> <button onClick={()=>{ menuStore.closeLeftPanel(); }}>Close left panel</button> </div> ); export default Component;
ããã«ãééãããã¿ã³ã®ãã¢å
ã ãã®ã³ã³ããŒãã³ããããŒã ããŒãžã«è¿œå ããŸãã 以äžãååŸããå¿
èŠããããŸãã
ãã©ãŠã¶ã§ã¯ã次ã®ããã«ãªããŸãã
ããã§ãããã«èªäœããã ãã§ãªããå¥ã®ã³ã³ããŒãã³ããããããã«ã®ç¶æ
ãå¶åŸ¡ã§ããŸãã
泚ïŒããšãã°ããå·Šããã«ãéããããã¿ã³ãã¯ãªãã¯ããŠåãã¢ã¯ã·ã§ã³ãè€æ°åå®è¡ããå Žåããããã¬ãŒã§ã¢ã¯ã·ã§ã³ãæ©èœããããšã確èªã§ããŸãããåå¿ã¯çºçããŸããã ããã¯ãç¶æ
ã倿ŽãããŠããããçŽç²ãªåå¿ã³ã³ããŒãã³ãã®å Žåã®ããã«ãäœåãªãã³ãŒããèšè¿°ããå¿
èŠããªããããmobxã¯ã³ã³ããŒãã³ãããªãã€ã¬ã¯ãããªãããšãæå³ããŸããç§ãã¡ã®ã¢ãããŒããå°ãã°ããæ€èšããããšã¯æ®ã£ãŠããŸããåºèãšååããã®ã¯è¯ãããšã§ããããããžã§ã¯ãå
šäœã§å庫ã®èŒžå
¥ã忣ãããã®ã¯ãã§ãã mobx-reactã§ã¯ã
Providerã¯ãã®ãããªç®çã®ããã«ç»å ŽããŸãã
ïŒProviderããã³injectãåç
§ïŒ ãreactcontextã䜿çšããŠã¹ãã¢ãïŒã ãã§ãªãïŒåå«ã«æž¡ãããšãã§ããã³ã³ããŒãã³ãã§ãã ãããè¡ãã«ã¯ãapp.jsã®ã«ãŒãã³ã³ããŒãã³ãããããã€ããŒã§ã©ããããŸãã
app.js import React from 'react'; import { Provider } from 'mobx-react'; import { useStrict } from 'mobx'; import Menu from '../components/menu'; import leftMenuStore from '../stores/menu-store'; import './global.css'; import style from './app.css'; useStrict(true); const stores = { leftMenuStore }; const App = props => ( <Provider { ...stores }> <div className={style['app-container']}> <Menu /> <div className={style['page-container']}> {props.children} </div> </div> </Provider> ); export default App;
ããã«ãã¹ãŠã®é¢ä¿è
ïŒ1ã€ããããŸãïŒãã€ã³ããŒãããå°éå
·ãä»ããŠãããã€ããŒã«è»¢éããŸãã ãããã€ããŒã¯ã³ã³ããã¹ããæäœãããããé¢ä¿è
ã¯ãã¹ãŠã®åã³ã³ããŒãã³ãã§å©çšã§ããŸãã ãŸããmenu.jsã³ã³ããŒãã³ãã2ã€ã«åå²ããŠã
ãæããªãã³ã³ããŒãã³ããšãã¹ããŒããªãã³ã³ããŒãã³ããååŸã
ãŸã ã
ã³ã³ããŒãã³ã/ã¡ãã¥ãŒ/menu.js import React from 'react'; import cn from 'classnames'; import styles from './style.css'; const Menu = props => ( <nav className={cn(styles.menu, { [styles.active]: props.isOpenLeftPanel })}> <div onClick={props.toggleMenu} className={styles['toggle-btn']}>â°</div> </nav> ); export default Menu;
ã³ã³ããŒãã³ã/ã¡ãã¥ãŒ/index.js import React from 'react'; import { observer, inject } from 'mobx-react'; import Menu from './menu' const Component = inject('leftMenuStore')(observer(({ leftMenuStore }) => ( <Menu toggleMenu={() => leftMenuStore.toggleLeftPanel()} isOpenLeftPanel={leftMenuStore.isOpenLeftPanel} /> ))); Component.displayName = "MenuContainer"; export default Component;
ãSillyãã¯ãããã«ãšåãæ¿ãçšã®ã³ãŒã«ããã¯ãéããŠãããéããŠãããã«é¢ããããŒã¿ãå°éå
·ãéããŠåãåãéåžžã®ã¹ããŒãã¬ã¹ã³ã³ããŒãã³ãã§ãããããç§ãã¡ã«ãšã£ãŠè峿·±ããã®ã§ã¯ãããŸããã
圌ã®ã©ãããŒãèŠããšãã£ãšé¢çœãã§ããããã§ã¯
HOCãèŠãŠãå¿
èŠãªã¹ãã¢ããã®å Žåã¯ãleftMenuStoreãããªãã¶ãŒããŒã«ã©ããããããæããªã³ã³ããŒãã³ããã«æž¡ãã³ã³ããŒãã³ããšããŠæ³šå
¥ããŸãã leftMenuStoreãæ³šå
¥ããã®ã§ããªããžããªã¯å°éå
·ãä»ããŠå©çšå¯èœã«ãªããŸããã
å·Šããã«ã³ã³ãããŒã©ãŒã§è¡ãããšãšã»ãŒåãããšïŒ
ã³ã³ããŒãã³ã/ left-menu-controller / left-menu-controller.js import React from 'react'; import style from './styles.css'; const LeftPanelController = props => ( <div className={style.container}> <button onClick={() => props.openPanel()}>Open left panel</button> <button onClick={() => props.closePanel()}>Close left panel</button> </div> ); export default LeftPanelController;
ã³ã³ããŒãã³ã/å·Šã¡ãã¥ãŒã³ã³ãããŒã©ãŒ/ index.js import React from 'react'; import { inject } from 'mobx-react'; import LeftPanelController from './left-panel-controller'; const Component = inject('leftMenuStore')(({ leftMenuStore }) => { return ( <LeftPanelController openPanel={() => leftMenuStore.openLeftPanel()} closePanel={() => leftMenuStore.closeLeftPanel()} /> ); }); LeftPanelController.displayName = 'LeftPanelControllerContainer'; export default Component;
å¯äžã®éãã¯ããã®ã³ã³ããŒãã³ãã«å¯ŸããŠäœãåæç»ããå¿
èŠããªãããããªãã¶ãŒããŒã䜿çšããªãããšã§ãããªããžããªã®openLeftPanelïŒïŒããã³closeLeftPanelïŒïŒã¡ãœããã®ã¿ãå¿
èŠã§ãã
泚ïŒdisplayNameã䜿çšããŠã³ã³ããŒãã³ãã«ååãä»ããŸããããã¯ãããã°ã«äŸ¿å©ã§ããããšãã°ãæ€çŽ¢ãéããŠã³ã³ââããŒãã³ããèŠã€ããããšãã§ããããã«ãªããŸãã ãã¹ãŠç°¡åã§ãããµãŒããŒããããŒã¿ãååŸãããã§ãã¯ããã¯ã¹ä»ãã®ãŠãŒã¶ãŒã®ãªã¹ãã«ããŸãããã
ãµãŒããŒã«ã¢ã¯ã»ã¹ããã/ usersãã«ãŒãã远å ããŠãŠãŒã¶ãŒãååŸããŸãã
server.js const USERS = [ { id: 1, name: "Alexey", age: 30 }, { id: 2, name: "Ignat", age: 15 }, { id: 3, name: "Sergey", age: 26 }, ]; ... app.get("/users", function(req, res) { setTimeout(() => { res.send(USERS); }, 1000); });
æå³çã«é
å»¶ã远å ããŠããµãŒããŒå¿çã®ééãé·ããŠãã¢ããªã±ãŒã·ã§ã³ãæ£åžžã«åäœããŠããããšã確èªããŸãã
次ã«å¿
èŠ
ãŠãŒã¶ãŒã¹ãã¢ïŒ import { observable, computed, action, asMap, autorun } from 'mobx'; class User { @observable user = observable.map(); constructor(userData = {}, checked = false) { this.user.merge(userData); this.user.set("checked", checked); } @computed get userInfo() { return `${this.user.get("name")} - ${this.user.get("age")}`; } @action toggle() { this.user.set("checked", !this.user.get("checked")); } } class UserStore { @observable users; constructor() { this.users = []; this.fetch(); } @computed get selectedCount() { return this.users.filter(userStore => { return userStore.user.get("checked"); }).length; } getUsers() { return this.users; } @action fetch() { fetch('/users', { method: 'GET' }) .then(res => res.json()) .then(json => this.putUsers(json)); } @action putUsers(users) { let userArray = []; users.forEach(user => { userArray.push(new User(user)); }); this.users = userArray; } } const userStore = new UserStore(); autorun(() => { console.log(userStore.getUsers().toJS()); }); export default userStore; export { UserStore };
ããã§ã¯ããŠãŒã¶ãŒããããã£ãæã€Userã¯ã©ã¹ã«ã€ããŠèª¬æããŸãã Mobxã«ã¯
observable.mapããŒã¿åãããããŠãŒã¶ãŒã説æããã®ã«ã¡ããã©ããã§ãã 倧ãŸãã«èšããšã芳枬å¯èœãªãªããžã§ã¯ããååŸããŸããããã«ãç¹å®ã®ãã£ãŒã«ãã®å€åã芳枬ã§ããŸãã ã²ãã¿ãŒãã»ãã¿ãŒãããã³ãã®ä»ã®ãã«ããŒã¡ãœããã䜿çšã§ããŸãã ããšãã°ããmergeãã䜿çšããã³ã³ã¹ãã©ã¯ã¿ãŒã§ã¯ãuserDataãããŠãŒã¶ãŒã«ãã£ãŒã«ããç°¡åã«ã³ããŒã§ããŸãã ãªããžã§ã¯ãã«å€ãã®ãã£ãŒã«ããå«ãŸããå Žåãããã¯éåžžã«äŸ¿å©ã§ãã ãŸãããŠãŒã¶ãŒã«é¢ããæ
å ±ãååŸããããã«ããŠãŒã¶ãŒã®ç¶æ
ãšèšç®å€ãåãæ¿ããã¢ã¯ã·ã§ã³ã1ã€äœæããŸãã
以äžã¯ãç£èŠå¯Ÿè±¡ããŠãŒã¶ãŒã®é
åã§ããåŽèªäœã«ã€ããŠèª¬æããŠããŸãã ã³ã³ã¹ãã©ã¯ã¿ãŒã§ãã¡ãœããããã«ããŠãµãŒããŒãããŠãŒã¶ãŒãååŸããã¢ã¯ã·ã§ã³putUsersãä»ããŠç©ºã®é
åã«ãŠãŒã¶ãŒãå
¥åããŸãã æåŸã«ããã§ãã¯ããããŠãŒã¶ãŒã®èšç®ãããæ°ãè¿ãã¡ãœããã远å ããŸãã
泚ïŒèŠ³æž¬å€ã倿Žãããå Žåã autorunã¯é¢æ°ãèªåçã«å®è¡ããŸãã ããšãã°ãããã§ã¯ãã¹ãŠã®ãŠãŒã¶ãŒãã³ã³ãœãŒã«ã«è¡šç€ºãããŸãã getUsersïŒïŒã¡ãœããã䜿çšããŠãŠãŒã¶ãŒãååŸããããšãããšãæ»ãå€ã®åã¯é
åã§ã¯ãªããObservableArrayã§ããããšãããããŸãã ãªãã¶ãŒããã«ãªããžã§ã¯ããjavascriptæ§é ã«å€æããã«ã¯ã toJSïŒïŒã䜿çšããŸããapp.jsã§ã¯ãåå«ã䜿çšã§ããããã«æ°ãããŠãŒã¶ãŒã¹ãã¢ã远å ããããšãå¿ããªãã§ãã ããã
Reactã³ã³ããŒãã³ããã³ã³ããŒãã³ããã£ã¬ã¯ããªã«è¿œå ããŸãã
ãŠãŒã¶ãŒãªã¹ã/ index.js import React from 'react'; import { observer, inject } from 'mobx-react'; import UserList from './user-list'; const Component = inject('userStore')(observer(({ userStore }) => { return ( <UserList users={userStore.getUsers()} selectedUsersCount={userStore.selectedCount} /> ); })); Component.displayName = 'UserList'; export default Component;
ããã§ã¯ãã§ã«ã©ãããŒã«ç²ŸéããŠããããŠãŒã¶ãŒã®é
åãšãã§ãã¯ããããŠãŒã¶ãŒã®æ°ãå°éå
·ãä»ããŠè»¢éããŸãã
user-list / user-list.js import React from 'react'; import UserListItem from './user-list-item'; import style from './styles.css'; const UserList = props => { return ( <div className={style.container}> <ul> {props.users.map(userStore => { return ( <UserListItem key={userStore.user.get('id')} isChecked={userStore.user.get('checked')} text={userStore.userInfo} onToggle={() => userStore.toggle()} />); })} </ul> <span>{`Users:${props.users.length}`}</span> <span>{`Selected users: ${props.selectedUsersCount}`}</span> </div> ); }; export default UserList;
ãŠãŒã¶ãŒã®ãªã¹ããšãã®çªå·ã«é¢ããæ
å ±ã衚瀺ããŸãã ã¹ãã¢ã®toggleïŒïŒã¡ãœãããpropsçµç±ã§æž¡ããŸãã
user-list / user-list-item.js import React from 'react'; const UserListItem = props => ( <li><input type="checkbox" checked={props.isChecked} onClick={() => props.onToggle()} />{props.text} </li> ); export default UserListItem;
1人ã®ãŠãŒã¶ãŒãã¬ã³ããªã³ã°ããŸãã
ã¹ã¿ã€ã«ã远å ãã宿ããã³ã³ããŒãã³ããããŒã ããŒãžã«ããã¯ããŸãã ãã¹ãŠã®æºåãã§ããŸããïŒ
github ïŒããã§ãã¯ããã¯ã¹ãæäœããŠããã¹ãŠã®ã¡ãœãããæ©èœããããšã確èªã§ããŸãã
ãã®çµæãmobxã®ãã¹ãŠã®å¯èœæ§ãèæ
®ããŠãmobxããªã¢ã¯ã·ã§ã³ãšé£æºããŠåäœããæ¹æ³ãèŠãŸããããã®ãããªãœãªã¥ãŒã·ã§ã³ã«ã¯çåœæš©ããããšä»®å®ã§ããŸãã Mobxã¯ãReactã¢ããªã±ãŒã·ã§ã³ã«å¯Ÿããç¶æ
ãããŒãžã£ãŒã®è²¬ä»»ãåŠçããå®è£
ã®ããã®è±å¯ãªæ©èœãæäŸããŸãã