ãåå¿æ§ããšããèšèã¯éåžžãåç §FRPãšããŠRx.jsãæããŸãã ãã ãããã®ããŒãã«é¢ããHabréã®äžé£ã®æè¿ã®èšäºïŒ [1] ã [2] ã [3] ïŒã¯ãRxã®ãœãªã¥ãŒã·ã§ã³ã®æ±ãã«ããããšã瀺ããŸããã Rxã¯å€§ãã匷åã§ããããããŒã®æœè±¡åèªäœã瀺åããåé¡ã®è§£æ±ºã«æé©ã§ãïŒå®éã«ã¯ãããã¯äž»ã«éåæã¿ã¹ã¯ã®èª¿æŽã§ãïŒã ããããããšãã°ãRxã§åçŽãªåæãã©ãŒã æ€èšŒãäœæããŸããïŒ åœŒã¯éåžžã®åœä»€åã¢ãããŒããšæ¯èŒããŠããªãã®æéãç¯çŽããŸããïŒ
mrrã¯ãFRFãç¹å®ã®ãã¹ããªãŒãã³ã°ãåé¡ã ãã§ãªããæãäžè¬çãªæ¥åžžã®ããã³ããšã³ãã¿ã¹ã¯ã§ã䟿å©ã§å¹æçãªãœãªã¥ãŒã·ã§ã³ã«ãªãåŸãããšã蚌æããè©Šã¿ã§ãã
ãªã¢ã¯ãã£ãããã°ã©ãã³ã°ã¯éåžžã«åŒ·åãªæœè±¡åã§ãããçŸæç¹ã§ã¯ããã³ããšã³ãã«2ã€ã®æ¹æ³ã§ååšããŸãã
- åå¿å€æ°ïŒèšç®å€æ°ïŒïŒã·ã³ãã«ã§ä¿¡é Œæ§ãé«ããçŽæçã§ãããRPã®å¯èœæ§ã¯å®å šã«ã¯æããã«ãããŠããŸãã
- RxãBaconãªã©ã®ã¹ããªãŒã ãæäœããããã®ã©ã€ãã©ãªïŒåŒ·åã§ãããããªãè€éãªãããå®éã®äœ¿çšç¯å²ã¯ç¹å®ã®ã¿ã¹ã¯ã«éå®ãããŸãã
Mrrã¯ãããã®ã¢ãããŒãã®å©ç¹ãçµã¿åãããŠããŸãã Rx.jsãšã¯ç°ãªããmrrã«ã¯ãŠãŒã¶ãŒãè¿œå ããŠæ¡åŒµã§ããçãAPIããããŸãã å€æ°ã®ã¡ãœãããšæŒç®åã®ä»£ããã«-ObservableïŒããããšã³ãŒã«ãïŒãSubjectãªã©ã®ä»£ããã«4ã€ã®åºæ¬æŒç®å -1ã€ã®æœè±¡åïŒã¹ããªãŒã ã ãŸããmrrã«ã¯ãã³ãŒãã®èªã¿ããããå€§å¹ ã«è€éã«ããå¯èœæ§ã®ããè€éãªæŠå¿µïŒã¡ã¿ã¹ããªãŒã ãªã©ïŒããããŸããã
ãã ããmrrã¯ãæ°ããæ¹æ³ã§ç°¡çŽ åãããRxãã§ã¯ãããŸããã Rxãšåãåºæ¬ååã«åºã¥ããŠãmrrã¯ã¢ããªã±ãŒã·ã§ã³ã®ã°ããŒãã«ããã³ããŒã«ã«ïŒã³ã³ããŒãã³ãã¬ãã«ïŒç¶æ ã管çããããã倧ããªãããã§ãããšäž»åŒµããŠããŸãã ãªã¢ã¯ãã£ãããã°ã©ãã³ã°ã®å ã®æŠå¿µã¯éåæã¿ã¹ã¯ã§åäœããããšãç®çãšããŠããŸããããmrrã¯éåžžã®åæã¿ã¹ã¯ã«ãªã¢ã¯ãã£ãã¢ãããŒããããŸã䜿çšããŠããŸãã ããããç·FRPãã®ååã§ãã
å€ãã®å ŽåãReactã§ã¢ããªã±ãŒã·ã§ã³ãäœæããéã«ãè€æ°ã®ç°çš®ãã¯ãããžãŒã䜿çšãããŸãïŒã³ã³ããŒãã³ãç¶æ ã®åæ§æïŒãŸãã¯ããã«-ããã¯ïŒãã°ããŒãã«ç¶æ ã®Redux / mobxãå¯äœçšã管çãéåæã調æŽããããã®redux-observableïŒãŸãã¯thunk / sagaïŒãæã€Rxãšãã£ã¿ãŒã®ã¿ã¹ã¯ã åãã¢ããªã±ãŒã·ã§ã³å ã®ããŸããŸãªã¢ãããŒãããã¯ãããžãŒã®ããµã©ããã®ä»£ããã«ãmrrã䜿çšãããšãåäžã®ãã¯ãããžãŒãšãã©ãã€ã ã䜿çšã§ããŸãã
mrrã€ã³ã¿ãŒãã§ãŒã¹ã¯ãRxãåæ§ã®ã©ã€ãã©ãªãŒãšã倧ããç°ãªããŸã-ãã宣èšçã§ãã åå¿æ§ã®æœè±¡åãšå®£èšçã¢ãããŒãã®ãããã§ãmrrã¯è¡šçŸåè±ãã§ç°¡æœãªã³ãŒããæžãããšãã§ããŸãã ããšãã°ã mrräžã®æšæºã®TodoMVCã¯ã50è¡æªæºã®ã³ãŒãïŒJSXãã³ãã¬ãŒããã«ãŠã³ãããŸããïŒã§æžã¿ãŸãã
ããããååãªåºåã ã軜ããRPãšãéããRPã®å©ç¹ã1ã€ã®ããã«ã«ãŸãšããããšãã§ããŸããããå€æããå¿ èŠããããŸãããæåã«ã³ãŒãäŸãèªãã§ãã ããã
TodoMVCã¯ãã§ã«ããªãçãã®ã§ãGithubãŠãŒã¶ãŒã«é¢ããããŒã¿ãããŠã³ããŒãããäŸã¯ããŸãã«ãåå§çã§ãã©ã€ãã©ãªã®æ©èœãæããããšãã§ããŸããã ééãã±ãããè³Œå ¥ããããã®æ¡ä»¶ä»ãã¢ããªã±ãŒã·ã§ã³ã®äŸãšããŠãmrrãæ€èšããŸãã UIã«ã¯ãéå§ã¹ããŒã·ã§ã³ãšçµäºã¹ããŒã·ã§ã³ãæ¥ä»ãéžæããããã®ãã£ãŒã«ãããããŸãã 次ã«ãããŒã¿ãéä¿¡ããåŸãå©çšå¯èœãªåè»ãšãã®äžã®å Žæã®ãªã¹ããè¿ãããŸãã ç¹å®ã®åè»ãšè»ã®çš®é¡ãéžæãããšããŠãŒã¶ãŒã¯ä¹å®¢ããŒã¿ãå ¥åããŠããã±ããããã¹ã±ããã«è¿œå ããŸãã è¡ãã
ã¹ããŒã·ã§ã³ãšæ¥ä»ãéžæã§ãããã©ãŒã ãå¿ èŠã§ãã
ã¹ããŒã·ã§ã³ãå ¥åããããã®ãªãŒãã³ã³ããªãŒããã£ãŒã«ããäœæããŸãã
import { withMrr } from 'mrr'; const stations = [ '', '', '', ' ', ... ] const Tickets = withMrr({ // $init: { stationFromOptions: [], stationFromInput: '', }, // - "" stationFromOptions: [str => stations.filter(s => s.indexOf(str)===0), 'stationFromInput'], }, (state, props, $) => { return (<div> <h3> </h3> <div> : <input onChange={ $('stationFromInput') } /> </div> <ul className="stationFromOptions"> { state.stationFromOptions.map(s => <li>{ s }</li>) } </ul> </div>); }); export default Tickets;
mrrã³ã³ããŒãã³ãã¯withMrré¢æ°ã䜿çšããŠäœæãããŸãããã®é¢æ°ã¯ããªã¢ã¯ãã£ããªã³ã¯å³ïŒãããŒã®èª¬æïŒãšã¬ã³ããªã³ã°é¢æ°ãåãå ¥ããŸãã ã¬ã³ããªã³ã°é¢æ°ã¯ãç¶æ ã ãã§ãªãã³ã³ããŒãã³ãã®å°éå ·ã«ãæž¡ãããŸãããçŸåšã¯mrrã«ãã£ãŠå®å šã«å¶åŸ¡ãããŠããŸãã ããã«ã¯ãåæå€ïŒãããã¯$ initïŒãå«ãŸããåå¿ã»ã«ã®æ°åŒå€ã«ãã£ãŠèšç®ãããŸãã
ããã§ã2ã€ã®ã»ã«ïŒãŸãã¯åããã®ã§ãã2ã€ã®ã¹ããªãŒã ïŒããããŸãïŒ stationFromInput ã $ãã«ããŒã䜿çšãããŠãŒã¶ãŒå ¥åããã®å€ïŒããŒã¿å ¥åèŠçŽ ã«å¯ŸããŠevent.target.valueãããã©ã«ãã§æž¡ãïŒãããã³ãããã掟çããã»ã«ååã§äžèŽããã¹ããŒã·ã§ã³ã®é åãå«ãstationFromOptions ã
stationFromOptionsã®å€ã¯ãé¢æ°ã䜿çšããŠèŠªã»ã«ãå€æŽããããã³ã«èªåçã«èšç®ãããŸãïŒExcelã®æ°åŒã«äŒŒãã æ°åŒ ããšåŒã°ããmrrçšèªã§ïŒã mrråŒã®æ§æã¯åçŽã§ããæåã¯ãã»ã«ã®å€ãèšç®ããé¢æ°ïŒãŸãã¯æŒç®åïŒã§ãã次ã«ããã®ã»ã«ãäŸåããã»ã«ã®ãªã¹ãããããŸããå€ã¯é¢æ°ã«è»¢éãããŸãã ãã®ãããªå¥åŠãªäžèŠããæ§æã«ã¯å€ãã®å©ç¹ããããŸãããããã«ã€ããŠã¯åŸã§æ€èšããŸãã ãããŸã§ã®ãšãããããã§ã®mrrããžãã¯ã¯ãVueãSvelteãããã³ãã®ä»ã®ã©ã€ãã©ãªã§äœ¿çšãããèšç®å¯èœãªå€æ°ã䜿çšããéåžžã®ã¢ãããŒãã«äŒŒãŠããŸãããå¯äžã®éãã¯çŽç²ãªé¢æ°ã䜿çšã§ããããšã§ãã
å ¥åãã£ãŒã«ãã®ãªã¹ãããéžæããã¹ããŒã·ã§ã³ã®çœ®æãå®è£ ããŸãã ãŠãŒã¶ãŒãããããã®ã¹ããŒã·ã§ã³ãã¯ãªãã¯ããåŸã«ãã¹ããŒã·ã§ã³ã®ãªã¹ããé衚瀺ã«ããããšãå¿ èŠã§ãã
const Tickets = withMrr({ $init: { stationFromOptions: [], stationFromInput: '', }, stationFromOptions: [str => stations.filter(s => str.indexOf(a) === 0), 'stationFromInput'], stationFrom: ['merge', 'stationFromInput', 'selectStationFrom'], optionsShown: ['toggle', 'stationFromInput', 'selectStationFrom'], }, (state, props, $) => { return (<div> <div> : <input onChange={ $('stationFromInput') } value={ state.stationFrom }/> </div> { state.optionsShown && <ul className="stationFromOptions"> { state.stationFromOptions.map(s => <li onClick={ $('selectStationFrom', s) }>{ s }</li>) } </ul> } </div>); });
ã¹ããŒã·ã§ã³ã®ãªã¹ãã§$ãã«ããŒã䜿çšããŠäœæãããã€ãã³ããã³ãã©ãŒã¯ãåãªãã·ã§ã³ã®åºå®å€ãåºåããŸãã
mrrã¯ããã®å®£èšçã¢ãããŒãã«äžè²«æ§ããããããããçªç¶å€ç°ãšã¯ç¡é¢ä¿ã§ãã ã¹ããŒã·ã§ã³ãéžæããåŸãã»ã«å€ãã匷å¶ãå€æŽããããšã¯ã§ããŸããã 代ããã«ãæ°ããstationFromã»ã«ãäœæããŸãã ãã®ã»ã«ã¯ãããŒãžã¹ããªãŒã æŒç®åïŒRxã®è¿äŒŒå€ã¯composeLatestïŒã䜿çšããŠããŠãŒã¶ãŒå ¥åïŒ stationFromInput ïŒãšã¹ããŒã·ã§ã³éžæïŒ selectStationFrom ïŒã®2ã€ã®ã¹ããªãŒã ã®å€ãåéããŸãã
ãŠãŒã¶ãŒãäœããå ¥åããåŸã«ãªãã·ã§ã³ã®ãªã¹ãã衚瀺ãããªãã·ã§ã³ã®1ã€ãéžæããåŸã«é衚瀺ã«ããŸãã optionsShownã»ã«ã¯ãä»ã®ã»ã«ã®å€æŽã«å¿ããŠããŒã«å€ãåããªãã·ã§ã³ã®ãªã¹ãã®å¯èŠæ§ãæ åœããŸãã ããã¯ãæ§æç³ãååšããéåžžã«äžè¬çãªãã¿ãŒã³ã ãã°ã«æŒç®åã§ãã æåã®åŒæ°ïŒã¹ããªãŒã ïŒãå€æŽããããšã»ã«ã®å€ãtrueã«èšå®ããã2çªç®ã®åŒæ°ãfalseã«èšå®ãããŸã ã
å ¥åããããã¹ããã¯ãªã¢ãããã¿ã³ãè¿œå ããŸãã
const Tickets = withMrr({ $init: { stationFromOptions: [], stationFromInput: '', }, stationFromOptions: [str => stations.filter(s => str.indexOf(a) === 0), 'stationFromInput'], clearVal: [a => '', 'clear'], stationFrom: ['merge', 'stationFromInput', 'selectStationFrom', 'clearVal'], optionsShown: ['toggle', 'stationFromInput', 'selectStationFrom'], }, (state, props, $) => { return (<div> <div> : <input onChange={ $('stationFromInput') } value={ state.stationFrom }/> { state.stationFrom && <button onClick={ $('clear') }></button> } </div> { state.optionsShown && <ul className="stationFromOptions"> { state.stationFromOptions.map(s => <li onClick={ $('selectStationFrom', s) }>{ s }</li>) } </ul> } </div>); });
å ¥åãã£ãŒã«ãã®ããã¹ãã®å 容ãæ åœããstationFromã»ã«ã¯ã2ã€ã®ã¹ããªãŒã ããã§ã¯ãªãã3ã€ã®ã¹ããªãŒã ããå€ãåéããŸãã ãã®ã³ãŒãã¯åçŽåã§ããŸãã [* formula *ã* ... cell arguments *]ãšãã圢åŒã®mrrã³ã³ã¹ãã©ã¯ãã¯ãLispã®SåŒã«äŒŒãŠãããLispã®ããã«ããã®ãããªæ§é ãä»»æã«çžäºã«ãã¹ãã§ããŸãã
ç¡é§ãªclearValã»ã«ãåãé€ããã³ãŒããçãããŸãããã
stationFrom: ['merge', 'stationFromInput', 'selectStationFrom', [a => '', 'clear']],
åœä»€åã¹ã¿ã€ã«ã§æžãããããã°ã©ã ã¯ãçµç¹åãããŠããªãããŒã ãšæ¯èŒããããšãã§ããŸããããŒã ã¯åžžã«ãäºãã«äœãã泚æããŸãïŒã¡ãœããåŒã³åºããšå€æŽã®å€æŽããå§ãããŸãïŒãããã«ãäž¡æ¹ã®ãããŒãžã£ãŒã¯éšäžã§ããããã®éãåæ§ã§ãã 宣èšçãªããã°ã©ã ã¯ãå察ã®ãŠãŒããã¢çãªå³ã«äŒŒãŠããŸããã€ãŸãã誰ããã©ã®ãããªç¶æ³ã§ãã©ã®ããã«è¡åãã¹ãããæ確ã«ç¥ã£ãŠããéå£ã§ãã ãã®ãããªããŒã ã§ã¯æ³šæã®å¿ èŠã¯ãããŸãã;誰ããã¡ããã©åœŒãã®å Žæã«ããŠãèµ·ãã£ãŠããããšã«åå¿ããŠåããŠããŸãã
ã€ãã³ãã®èãããããã¹ãŠã®çµæã説æãã代ããã«ïŒç¹å®ã®çªç¶å€ç°ãèµ·ããããã«èªãïŒããã®ã€ãã³ããçºçããå¯èœæ§ã®ãããã¹ãŠã®ã±ãŒã¹ã説æããŸãã ã»ã«ãä»ã®ã»ã«ã®å€æŽã«ã©ã®ãããªå€ãåããã ãããŸã§ã®å°ããªäŸã§ã¯ãstationFromã»ã«ãšãã®å€ã«åœ±é¿ãã3ã€ã®ç¶æ³ã«ã€ããŠèª¬æããŸããã åœä»€åã³ãŒãã«æ £ããŠããããã°ã©ããŒã«ãšã£ãŠããã®ã¢ãããŒãã¯çããããã«æãããããããŸããïŒãŸãã¯ãæŸèæãããåé¯ãããïŒã å®éãå®éã«èŠãããããã«ãã³ãŒãã®ç°¡æœãïŒããã³å®å®æ§ïŒã®ããã«åŽåãç¯çŽã§ããŸãã
éåææ§ã¯ã©ãã§ããïŒ ajaxã§ææ¡ãããã¹ããŒã·ã§ã³ã®ãªã¹ãããã«ã¢ããããããšã¯å¯èœã§ããïŒ åé¡ãããŸããïŒ æ¬è³ªçã«ãé¢æ°ãå€ãè¿ãããããã¹ãè¿ããã¯ãmrrã«ãšã£ãŠéèŠã§ã¯ãããŸããã mrr promiseãè¿ããããšããã®è§£æ±ºãåŸ ã£ãŠãåä¿¡ããããŒã¿ãã¹ããªãŒã ã«ãããã·ã¥ãããŸãã
stationFromOptions: [str => fetch('/get_stations?str=' + str).then(res => res.toJSON()), 'stationFromInput'],
ãŸããéåæé¢æ°ãæ°åŒãšããŠäœ¿çšã§ããããšãæå³ããŸãã ããè€éãªã±ãŒã¹ïŒãšã©ãŒåŠçãçŽæã®ã¹ããŒã¿ã¹ïŒã¯åŸã§æ€èšãããŸãã
åºçºé§ ãéžæããæ©èœãçšæãããŸããã å°çã¹ããŒã·ã§ã³ã«åããã®ãè€è£œããããšã¯æå³ããããŸãããåå©çšã§ããå¥ã®ã³ã³ããŒãã³ãã«å ¥ãã䟡å€ããããŸãã ããã¯èªåè£å®æ©èœãåããäžè¬åãããå ¥åã³ã³ããŒãã³ãã«ãªãããããã£ãŒã«ãã®ååãå€æŽããpropsã§é©åãªãªãã·ã§ã³ã»ãããååŸããããã®é¢æ°ãäœæããŸãã
const OptionsInput = withMrr(props => ({ $init: { options: [], }, val: ['merge', 'valInput', 'selectOption', [a => '', 'clear']], options: [props.getOptions, 'val'], optionsShown: ['toggle', 'valInput', 'selectOption'], }), (state, props, $) => <div> <div> <input onChange={ $('valInput') } value={ state.val } /> </div> { state.optionsShown && <ul className="options"> { state.options.map(s => <li onClick={ $('selectOption', s) }>{ s }</li>) } </ul> } { state.val && <div className="clear" onClick={ $('clear') }> X </div> } </div>)
ã芧ã®ãšãããmrrã»ã«ã®æ§é ãpropsã³ã³ããŒãã³ãã®é¢æ°ãšããŠæå®ã§ããŸãïŒãã ããåæåæã«1åã ãå®è¡ãããpropsã®å€æŽã«ã¯å¿çããŸããïŒã
ã³ã³ããŒãã³ãéã®éä¿¡
次ã«ããã®ã³ã³ããŒãã³ãã芪ã³ã³ããŒãã³ãã«æ¥ç¶ããmrrãé¢é£ã³ã³ããŒãã³ããããŒã¿ã亀æã§ããããã«ããæ¹æ³ã確èªããŸãã
const getMatchedStations = str => fetch('/get_stations?str=' + str).then(res => res.toJSON()); const Tickets = withMrr({ stationTo: 'selectStationFrom/val', stationFrom: 'selectStationTo/val', }, (state, props, $, connectAs) => { return (<div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div>); });
芪ã³ã³ããŒãã³ããåã³ã³ããŒãã³ãã«é¢é£ä»ããã«ã¯ã connectAsé¢æ°ïŒrenderé¢æ°ã®4çªç®ã®åŒæ°ïŒã䜿çšããŠãã©ã¡ãŒã¿ãŒãæž¡ãå¿ èŠããããŸãã ãã®å Žåãåã³ã³ããŒãã³ãã«ä»ãããååã瀺ããŸãã ãã®æ¹æ³ã§ã³ã³ããŒãã³ããã¢ã¿ããããããšã«ããããã®ååã§ãã®ã»ã«ã«ã¢ã¯ã»ã¹ã§ããŸãã ãã®å Žåãval cellsãèããŠããŸãã éãå¯èœã§ã-芪ã»ã«ã®åã³ã³ããŒãã³ããããªãã¹ã³ããŸãã
ã芧ã®ãšãããããã§mrrã¯å®£èšçãªã¢ãããŒãã«åŸããŸããonChangeã³ãŒã«ããã¯ã¯äžèŠã§ããconnectAsé¢æ°ã§åã³ã³ããŒãã³ãã®ååãæå®ããã ãã§ããã®åŸã»ã«ã«ã¢ã¯ã»ã¹ã§ããŸãã ãã®å Žåãã宣èšæ§ã®ããã«ãå¥ã®ã³ã³ããŒãã³ãã®äœæ¥ã«å¹²æžã®è åšã¯ãããŸããããã®äžã®äœãããå€æŽãããèœåã¯ãããŸãããå€ç°ããŠãããŒã¿ããèããããšããã§ããŸããã
ä¿¡å·ãšå€
次ã®æ®µéã§ã¯ãéžæãããã©ã¡ãŒã¿ãŒã«é©ããåè»ãæ€çŽ¢ããŸãã åœä»€åã®ã¢ãããŒãã§ã¯ãããããç¹å®ã®ããã»ããµãäœæããŠonSubmitãã©ãŒã ãéä¿¡ããŸããããã«ãããããã«ã¢ã¯ã·ã§ã³ïŒajaxãªã¯ãšã¹ããšçµæã®è¡šç€ºïŒãéå§ãããŸãã ããããããªããèŠããŠããããã«ãç§ãã¡ã¯äœãã泚æãããããšã¯ã§ããŸããïŒ ãã©ãŒã ã®ã»ã«ãã掟çããå¥ã®ã»ã«ã®ã»ããã®ã¿ãäœæã§ããŸãã ãã1ã€ã®ãªã¯ãšã¹ããæžããŸãããã
const getTrains = (from, to, date) => fetch('/get_trains?from=' + from + '&to=' + to + '&date=' + date).then(res => res.toJSON()); const Tickets = withMrr({ stationFrom: 'selectStationFrom/val', stationTo: 'selectStationTo/val', results: [getTrains, 'stationFrom', 'stationTo', 'date', 'searchTrains'], }, (state, props, $, connectAs) => { return (<div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div>); });
ãã ãããã®ãããªã³ãŒãã¯æåŸ ã©ããã«æ©èœããŸããã åŒæ°ã®ãããããå€æŽããããšãèšç®ïŒã»ã«å€ã®åèšç®ïŒãããªã¬ãŒããããããããšãã°ãæ€çŽ¢ããã¯ãªãã¯ããã ãã§ãªããæåã®ã¹ããŒã·ã§ã³ãéžæããçŽåŸã«ãªã¯ãšã¹ããéä¿¡ãããŸãã äžæ¹ã§ã¯ãã¹ããŒã·ã§ã³ãšæ¥ä»ãåŒã®åŒæ°ã«æž¡ãããå¿ èŠããããŸãããä»æ¹ã§ã¯ããããã®å€æŽã«å¿çããŸããã mrrã«ã¯ãããã·ããªã¹ãã³ã°ãšåŒã°ãããã®ããã®ãšã¬ã¬ã³ããªã¡ã«ããºã ããããŸãã
results: [getTrains, '-stationFrom', '-stationTo', '-date', 'searchTrains'],
ã»ã«åã®åã«ãã€ãã¹èšå·ãè¿œå ããã ãã§ãã çŸåšã çµæã¯searchTrainsã»ã«ãžã®å€æŽã«ã®ã¿å¿çããŸãã
ãã®å Žåã searchTrainsã»ã«ã¯ãã»ã«ä¿¡å·ããšããŠæ©èœãã stationFromãªã©ã®ã»ã«ã¯ãã»ã«å€ããšããŠæ©èœããŸãã ã·ã°ãã«ã»ã«ã®å Žåãå€ããæµãããç¬éã ããéèŠã§ãããã©ã®ãããªããŒã¿ã«ãªãã®ã-ãšã«ãããããã¯åã«trueã1ãŸãã¯ãã®ä»ïŒãã®å Žåããããã¯DOMã€ãã³ããªããžã§ã¯ãã«ãªããŸãïŒ ïŒ å€ã»ã«ã®å Žåããã®å€ã¯éèŠã§ãããåæã«ãã®å€åã®ç¬éã¯éèŠã§ã¯ãããŸããã ãããã®2çš®é¡ã®ã»ã«ã¯çžäºã«æä»çã§ã¯ãããŸãããå€ãã®ã»ã«ã¯ã·ã°ãã«ãšå€ã®äž¡æ¹ã§ãã mrrã®æ§æã¬ãã«ã§ã¯ããããã®2çš®é¡ã®ã»ã«ã¯ãŸã£ããç°ãªããŸãããããªã¢ã¯ãã£ãã³ãŒããèšè¿°ããéã«ã¯ããã®ãããªéããæŠå¿µçã«ç解ããããšãéåžžã«éèŠã§ãã
ã¹ããªãŒã ãåå²ãã
åè»ã®åº§åžãæ€çŽ¢ãããªã¯ãšã¹ãã«ã¯æéããããå ŽåããããããããŒããŒã衚瀺ãããšã©ãŒãçºçããå Žåã«å¯Ÿå¿ããå¿ èŠããããŸãã èªå解決ã«ãããã®ããã©ã«ãã®ã¢ãããŒãã«ã¯ããã§ã«ããã€ãã®çŽæããããŸãã
const Tickets = withMrr({ $init: { results: {}, } stationFrom: 'selectStationFrom/val', stationTo: 'selectStationTo/val', searchQuery: [(from, to, date) => ({ from, to, date }), '-stationFrom', '-stationTo', '-date', 'searchTrains'], results: ['nested', (cb, query) => { cb({ loading: true, error: null, data: null }); getTrains(query.from, query.to, query.date) .then(res => cb('data', res)) .catch(err => cb('error', err)) .finally(() => cb('loading', false)) }, 'searchQuery'], availableTrains: 'results.data', }, (state, props, $, connectAs) => { return (<div> <div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div> <div> { state.results.loading && <div className="loading">...</div> } { state.results.error && <div className="error"> . , . .</div> } { state.availableTrains && <div className="results"> { state.availableTrains.map((train) => <div />) } </div> } </div> </div>); });
ãã¹ããããæŒç®åã䜿çšãããšãããŒã¿ããµãã»ã«ã«ãå解ãã§ããŸãããã®ãããåŒã®æåã®åŒæ°ã¯ã³ãŒã«ããã¯ã§ããããµãã»ã«ïŒ1ã€ä»¥äžïŒã«ããŒã¿ããããã·ã¥ãã§ããŸãã ããã§ããšã©ãŒãPromiseã®ã¹ããŒã¿ã¹ãããã³åä¿¡ããããŒã¿ãæ åœããåå¥ã®ã¹ããªãŒã ãã§ããŸããã ãã¹ããããæŒç®åã¯éåžžã«åŒ·åãªããŒã«ã§ãããmrrã«ã¯æ°å°ãªãåœä»€ã®1ã€ã§ãïŒããŒã¿ãé 眮ããã»ã«ãæå®ããŸãïŒã ããŒãžæŒç®åã¯è€æ°ã®ã¹ã¬ããã1ã€ã«çµåããŸããããã¹ããããšã¹ã¬ããã¯è€æ°ã®ãµãã¹ã¬ããã«åå²ãããããããã®éã«ãªããŸãã
äžèšã®äŸã¯ãpromiseãæäœããæšæºçãªæ¹æ³ã§ããmrrã§ã¯ãpromiseæŒç®åãšããŠäžè¬åãããŠãããã³ãŒããççž®ã§ããŸãã
results: ['promise', (query) => getTrains(query.from, query.to, query.date), 'searchQuery'], // availableTrains: 'results.data',
ãŸããpromiseæŒç®åã¯ãææ°ã®promiseã®çµæã®ã¿ã䜿çšãããããã«ããŸãã
å©çšå¯èœãªåº§åžã衚瀺ããããã®ã³ã³ããŒãã³ãïŒç°¡åã«ããããã«ãããŸããŸãªçš®é¡ã®è»ãæåŠããŸãïŒ
const TrainSeats = withMrr({ selectSeats: [(seatsNumber, { id }) => new Array(Number(seatsNumber)).fill(true).map(() => ({ trainId: id })), '-seatsNumber', '-$props', 'select'], seatsNumber: [() => 0, 'selectSeats'], }, (state, props, $) => <div className="train"> â{ props.num } { props.from } - { props.to }. : { props.seats || 0 } { props.seats && <div> : <input type="number" onChange={ $('seatsNumber') } value={ state.seatsNumber || 0 } max={ props.seats } /> <button onClick={ $('select') }></button> </div> } </div>);
æ°åŒã§å°éå ·ã«ã¢ã¯ã»ã¹ããã«ã¯ãç¹å¥ãª$å°éå ·ããã¯ã¹ã«ç»é²ã§ããŸãã
const Tickets = withMrr({ ... selectedSeats: '*/selectSeats', }, (state, props, $, connectAs) => { ... <div className="results"> { state.availableTrains.map((train, i) => <TrainSeats key={i} {...train} {...connectAs('train' + i)}/>) } </div> }
ãéžæããã¿ã³ãã¯ãªãã¯ãããšãããã·ããªã¹ãã³ã°ã䜿çšããŠãéžæããå Žæã®æ°ãååŸããŸãã connectAsé¢æ°ã䜿çšããŠãååã³ã³ããŒãã³ãã芪ã«é¢é£ä»ããŸãã ãŠãŒã¶ãŒã¯ææ¡ãããåè»ã®ããããã§åº§åžãéžæã§ããããããã¹ã¯ã*ãã䜿çšããŠãã¹ãŠã®åã³ã³ããŒãã³ãã®å€æŽãèããŸãã
ãã ããåé¡ã¯æ¬¡ã®ãšããã§ãããŠãŒã¶ãŒã¯æåã«1ã€ã®åè»ã§åº§åžãè¿œå ãã次ã«å¥ã®åè»ã§åº§åžãè¿œå ã§ãããããæ°ããããŒã¿ã¯ä»¥åã®åè»ãç²ç ããŸãã ã¹ããªãŒã ããŒã¿ããèç©ãããæ¹æ³ ãããè¡ãããã«ããã¹ãããããã¡ãã«ãšäžç·ã«mrrã®åºç€ã圢æããã¯ããŒãžã£ãŒæŒç®åããããŸãïŒä»ã®ãã¹ãŠã¯ãããã3ã€ã«åºã¥ãæ§æã·ã¥ã¬ãŒã«ãããŸããïŒã
selectedSeats: ['closure', () => { let seats = []; // return selectedSeats => { seats = [...seats, selectedSeats]; return seats; } }, '*/selectSeats'],
æåã«ã¯ããŒãžã£ãŒã ïŒcomponentDidMountã§ïŒäœ¿çšãããšãåŒãè¿ãã¯ããŒãžã£ãŒãäœæãããŸãã ãããã£ãŠã圌女ã¯ã¯ããŒãžã£ãŒå€æ°ã«ã¢ã¯ã»ã¹ã§ããŸãã ããã«ãããã°ããŒãã«å€æ°ãšå ±æãããå¯å€ç¶æ ã®æ·±byã«é¥ãããšãªããå®å šãªæ¹æ³ã§åŒã³åºãéã§ããŒã¿ãä¿åã§ããŸãã ãããã£ãŠãã¯ããŒãžã£ã䜿çšãããšãã¹ãã£ã³ãªã©ã®RxæŒç®åã®æ©èœãå®è£ ã§ããŸãã ãã ãããã®æ¹æ³ã¯é£ããå Žåã«é©ããŠããŸãã 1ã€ã®å€æ°ã®å€ã®ã¿ãä¿åããå¿ èŠãããå Žåãç¹å¥ãªååã^ãã䜿çšããŠãã»ã«ã®åã®å€ãžã®ãªã³ã¯ã䜿çšããã ãã§ãã
selectedSeats: [(seats, prev) => [...seats, ...prev], '*/selectSeats', '^']
ããã§ããŠãŒã¶ãŒã¯éžæããåãã±ããã®å§ãšåãå ¥åããå¿ èŠããããŸãã
const SeatDetails = withMrr({}, (state, props, $) => { return (<div> { props.trainId } <input name="name" value={ props.name } onChange={ $('setDetails', e => ['name', e.target.value, props.i]) } /> <input name="surname" value={ props.surname } onChange={ $('setDetails', e => ['surname', e.target.value, props.i]) }/> <a href="#" onClick={ $('removeSeat', props.i) }>X</a> </div>); }) const Tickets = withMrr({ $init: { results: {}, selectedSeats: [], } stationFrom: 'selectStationFrom/val', stationTo: 'selectStationTo/val', searchQuery: [(from, to, date) => ({ from, to, date }), '-stationFrom', '-stationTo', '-date', 'searchTrains'], results: ['promise', (query) => getTrains(query.from, query.to, query.date), 'searchQuery'], availableTrains: 'results.data', selectedSeats: [(seats, prev) => [...seats, ...prev], '*/selectSeats', '^'] }, (state, props, $, connectAs) => { return (<div> <div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div> <div> { state.results.loading && <div className="loading">...</div> } { state.results.error && <div className="error"> . , . .</div> } { state.availableTrains && <div className="results"> { state.availableTrains.map((train, i) => <TrainSeats key={i} {...train} {...connectAs('train' + i)}/>) } </div> } { state.selectedSeats.map((seat, i) => <SeatDetails key={i} i={i} { ...seat } {...connectAs('seat' + i)}/>) } </div> </div>); });
selectedSeatsã»ã«ã«ã¯ãéžæããå Žæã®é åãå«ãŸããŸãã ãŠãŒã¶ãŒãåãã±ããã®ååãšå§ãå ¥åãããšãé åã®å¯Ÿå¿ããèŠçŽ ã®ããŒã¿ãå€æŽããå¿ èŠããããŸãã
selectedSeats: [(seats, details, prev) => { // ??? }, '*/selectSeats', '*/setDetails', '^']
æšæºçãªã¢ãããŒãã¯ç§ãã¡ã«ã¯é©ããŠããŸãããåŒã§ã¯ãã©ã®ã»ã«ãå€æŽãããããç¥ããããã«å¿ããŠå¿çããå¿ èŠããããŸãã ããŒãžæŒç®åã®åœ¢åŒã®1ã€ã圹ç«ã¡ãŸãã
selectedSeats: ['merge', { '*/selectSeats': (seats, prev) => { return [...prev, ...seats]; }, '*/setDetails': ([field, value, i], prev) => { prev[i][field] = value; return prev; }, '*/removeSeat': (i, prev) => { prev.splice(i, 1); return prev; }, }, '^'/*, */],
ããã¯Reduxã¬ãã¥ãŒãµãŒã«å°ã䌌ãŠããŸãããããæè»ã§åŒ·åãªæ§æãåããŠããŸãã ãŸããé åãå€æŽããããšãæããããšã¯ã§ããŸããã1ã€ã®ã»ã«ã®æ°åŒã®ã¿ãå¶åŸ¡ã§ããããã䞊åå€æŽã¯ããããé€å€ãããŸãïŒãã ããåŒæ°ãšããŠæž¡ãããé åã®å€æŽã¯ç¢ºãã«äŸ¡å€ããããŸããïŒã
ãªã¢ã¯ãã£ãã³ã¬ã¯ã·ã§ã³
ã»ã«ãããèªäœã«æ ŒçŽãããé åãå€æŽããããšãã®ãã¿ãŒã³ã¯éåžžã«äžè¬çã§ãã é åã«å¯Ÿãããã¹ãŠã®æäœã«ã¯ãæ¿å ¥ãå€æŽãåé€ã®3ã€ã®ã¿ã€ãããããŸãã ãããèšè¿°ããããã«ããšã¬ã¬ã³ããªcollæŒç®åããããŸãã selectedSeatsã®èšç®ãç°¡çŽ åããããã«äœ¿çšããŸãã
ããã¯ïŒ
selectedSeats: ['merge', { '*/selectSeats': (seats, prev) => { return [...prev, ...seats]; }, '*/setDetails': ([field, value, i], prev) => { prev[i][field] = value; return prev; }, '*/removeSeat': (i, prev) => { prev.splice(i, 1); return prev; }, 'addToCart': () => [], }, '^']
ã«ãªããŸããïŒ
selectedSeats: ['coll', { create: '*/selectSeats', update: '*/setDetails', delete: ['merge', '*/removeSeat', [() => ({}), 'addToCart']] }]
ãã ããsetDetailsã¹ããªãŒã ã®ããŒã¿åœ¢åŒãå°ãå€æŽããå¿ èŠããããŸãã
<input name="name" onChange={ $('setDetails', e => [{ name: e.target.value }, props.i]) } /> <input name="surname" onChange={ $('setDetails', e => [{ surname: e.target.value }, props.i]) }/>
collæŒç®åã䜿çšããŠãé åã«åœ±é¿ãäžãã3ã€ã®ã¹ã¬ãããèšè¿°ããŸãã ãã®å Žåã äœæã¹ããªãŒã ã«ã¯èŠçŽ èªäœãå«ãŸããŠããå¿ èŠããããèŠçŽ ã¯é åïŒéåžžã¯ãªããžã§ã¯ãïŒã«è¿œå ããå¿ èŠããããŸãã åé€ã¹ããªãŒã ã¯ã åé€ããèŠçŽ ã®ã€ã³ããã¯ã¹ïŒã* / removeSeatãã®äž¡æ¹ïŒãšãã¹ã¯ãåãå ¥ããŸãã ãã¹ã¯{}ã¯ãã¹ãŠã®èŠçŽ ãåé€ããŸããããšãã°ããã¹ã¯{nameïŒ 'Carl'}ã¯Carlãšããååã®ãã¹ãŠã®èŠçŽ ãåé€ããŸãã æŽæ°ã¹ããªãŒã ã¯ãå€ã®ãã¢ïŒèŠçŽ ïŒãã¹ã¯ãŸãã¯é¢æ°ïŒã§è¡ãå¿ èŠã®ããå€æŽïŒãšãå€æŽããå¿ èŠã®ããèŠçŽ ã®ã€ã³ããã¯ã¹ãŸãã¯ãã¹ã¯ãåãå ¥ããŸãã ããšãã°ã[{surnameïŒ 'Johnson'}ã{}]ã¯ããžã§ã³ãœã³ã®å§ãé åã®ãã¹ãŠã®èŠçŽ ã«èšå®ããŸãã
collæŒç®åã¯ãå éšã¯ãšãªèšèªã®ãããªãã®ã䜿çšããŠãã³ã¬ã¯ã·ã§ã³ã®æäœãšå®£èšã®äœæã容æã«ããŸãã
JsFiddle ã§ã®ã¢ããªã±ãŒã·ã§ã³ã®å®å šãªã³ãŒã ã
ç§ãã¡ã¯ãmrrã®å¿ èŠãªåºæ¬æ©èœã®ã»ãšãã©ãã¹ãŠã«ç²ŸéããŸããã è¹å€ã«æ®ã£ãŠããããªãéèŠãªãããã¯ã¯ã°ããŒãã«ãªè³ç£ç®¡çã§ããããããã以äžã®èšäºã§æ€èšãããã§ãããã ãã ããmrrã䜿çšããŠãã³ã³ããŒãã³ããŸãã¯é¢é£ããã³ã³ããŒãã³ãã®ã°ã«ãŒãå ã®ç¶æ ã管çã§ããããã«ãªããŸããã
çµè«
mrrã®åãšã¯äœã§ããïŒ
mrrã䜿çšãããšãReactã§æ©èœçã«åå¿ããã¹ã¿ã€ã«ã§ã¢ããªã±ãŒã·ã§ã³ãäœæã§ããŸãïŒmrrã¯ãMake Reactãåå¿çã«åŸ©å·åã§ããŸãïŒã mrrã¯éåžžã«è¡šçŸåè±ãã§ã-ã³ãŒãã®è¡ãæžãã®ã«è²»ããæéãå°ãªããªããŸãã
mrrã¯ãåºæ¬çãªæœè±¡æŠå¿µã®å°ããªã»ãããæäŸããŸããããã¯ããã¹ãŠã®å Žåã«ååã§ã-ãã®èšäºã§ã¯ãmrrã®ã»ãŒãã¹ãŠã®äž»èŠãªæ©èœãšãã¯ããã¯ã«ã€ããŠèª¬æããŸãããã®åºæ¬ã»ããïŒã«ã¹ã¿ã ãªãã¬ãŒã¿ãŒãäœæããæ©èœïŒãæ¡åŒµããããŒã«ããããŸããããã¥ã¢ã«ã®æ°çŸããŒãžãèªãããšãªããé¢æ°åããã°ã©ãã³ã°ã®çè«çãªæ·±ããå匷ããããšãªããçŸãã宣èšåã³ãŒããæžãããšãã§ããŸãã mrrèªäœã¯ãçŽç²ãªèšç®ãšç¶æ ã®å€åãåé¢ãã巚倧ãªã¢ããã§ãã
ä»ã®ã©ã€ãã©ãªã§ã¯ç°çš®ã¢ãããŒãïŒã¡ãœããã䜿çšããåœä»€åãšãªã¢ã¯ãã£ããã€ã³ããŒã䜿çšãã宣èšåïŒãå ±åããããšããããããŸãããããã°ã©ããŒã¯ãã¬ã¿ã¹ããã©ã³ãã ã«æ··åããŸãããmrrã«ã¯åäžã®åºæ¬ãšã³ãã£ãã£ïŒã³ãŒãã®åäžæ§ãšåäžæ§ã«å¯äžããã¹ããªãŒã ïŒããããŸããå¿«é©ãã䟿å©ããã·ã³ãã«ããããã°ã©ãã®æéã®ç¯çŽãmrrã®äž»ãªå©ç¹ã§ãïŒãããããmrrããmrrrrããšããŠãã³ãŒããããã€ãŸãç«ã®ç掻ã«æºè¶³ããŠããç«ã鳎ããïŒã
æ¬ ç¹ã¯äœã§ããïŒ
ãã©ã€ã³ãã䜿çšããããã°ã©ãã³ã°ã«ã¯ãé·æãšçæã®äž¡æ¹ããããŸããã»ã«åã®ãªãŒãã³ã³ããªãŒãã¯æ©èœããŸããããŸããã»ã«åãå®çŸ©ãããŠããå Žæãæ€çŽ¢ããããšãã§ããŸãããäžæ¹ãmrrã«ã¯åžžã«ã»ã«ã®åäœã決å®ãããå Žæã1ã€ã ããããã¹ãã¢ã®Reduxãã£ãŒã«ãã®å€ã決å®ãããå ŽæããŸãã¯ãã€ãã£ãã®setStateã䜿çšããå Žåã¯ç¶æ ãã£ãŒã«ããããã«å°ãªãå Žæãæ€çŽ¢ããªãããåçŽãªããã¹ãæ€çŽ¢ã§ç°¡åã«èŠã€ããããšãã§ããŸãé·ããªãå¯èœæ§ããããŸãã
誰ãããã«èå³ããããŸããïŒ
ãŸã第äžã«ãé¢æ°åããã°ã©ãã³ã°ã®æ¯æè -宣èšçã¢ãããŒãã®å©ç¹ãæãããªäººã ããã¡ãããClojureScriptã³ãŒã·ã£ãŒãœãªã¥ãŒã·ã§ã³ã¯æ¢ã«ååšããŸãããããã§ãããã補åã§ããç¶ããŸãããReactã¯ããŒã«ãæ¯é ããŸãããããžã§ã¯ãã§ãã§ã«Reduxã䜿çšããŠããå Žåã¯ãmrrã䜿çšããŠããŒã«ã«ç¶æ ã管çããå°æ¥çã«ã°ããŒãã«ã«åãæ¿ããããšãã§ããŸããçŸæç¹ã§æ°ãããã¯ãããžã䜿çšããäºå®ããªãå Žåã§ããmrrã¯äžè¬çãªç¶æ 管çã©ã€ãã©ãªãšã¯å€§ããç°ãªããããããªãã¿ã®ã¿ã¹ã¯ãæ°ãã芳ç¹ããèŠãŠãmrrãåŠçããŠãè³ã䌞ã°ããããšãã§ããŸãã
ããã¯ãã§ã«äœ¿çšã§ããŸããïŒ
ååãšããŠãã¯ã:)ã©ã€ãã©ãªã¯è¥ãããããããŸã§ããã€ãã®ãããžã§ã¯ãã§ç©æ¥µçã«äœ¿çšãããŠããŸããããåºæ¬çãªæ©èœã®APIã¯æ¢ã«ç¢ºç«ãããŠãããçŸåšãäž»ã«ããŸããŸãªããŒã·ã§ã³ïŒæ§æç³ïŒã§äœæ¥ãè¡ãããéçºãããã«å éããã³ä¿é²ããããã«èšèšãããŠããŸããã¡ãªã¿ã«ãmrrèªäœã®åçã«ã¯Reactã«åºæã®ãã®ã¯ãªããã©ã®ã³ã³ããŒãã³ãã©ã€ãã©ãªã§ã䜿çšã§ããããã«é©åãããããšãã§ããŸãïŒçµã¿èŸŒã¿ã®åå¿æ§ã®æ¬ åŠãŸãã¯ãã®ããã«äžè¬ã«åãå ¥ããããŠããã©ã€ãã©ãªã®ããã«ReactãéžæãããŸããïŒã
ãæž èŽããããšãããããŸããããã£ãŒãããã¯ãšå»ºèšçãªæ¹å€ã«æè¬ããŸãã