æ¬æžã®èè
ã§ãã翻蚳è
ã¯ãããã§ç¿»èš³ãå
¬éããŠããããããã§ã¯ãRxJSã䜿çšããåçŽãªReactã¢ããªã±ãŒã·ã§ã³ã®éçºããã»ã¹ã瀺ããããšè¿°ã¹ãŠããŸãã 圌ã«ãããšã圌ã¯RxJSã®å°é家ã§ã¯ãããŸããã圌ã¯ãã®ã©ã€ãã©ãªã®ç 究ã«åŸäºããŠãããç¥èã®ãã人ã
ã®å©ããæåŠããªãããã§ãã ãã®ç®æšã¯ãReactã¢ããªã±ãŒã·ã§ã³ãäœæããå¥ã®æ¹æ³ã«èŽè¡ã®æ³šæãåŒããèªè
ã«ç¬ç«ãã調æ»ãè¡ãããã«ä¿ãããšã§ãã ãã®è³æãRxJSã®çŽ¹ä»ãšåŒã¶ããšã¯ã§ããŸããã Reactéçºã§ãã®ã©ã€ãã©ãªã䜿çšããå€ãã®æ¹æ³ã®1ã€ãããã«ç€ºããŸãã

ããããã¹ãŠå§ãŸã£ãæ¹æ³
æè¿ã
ç§ã®ã¯ã©ã€ã¢ã³ãã¯
ã RxJSã䜿çšããŠReactã¢ããªã±ãŒã·ã§ã³ã®ç¶æ
ã管çããããšã«ã€ããŠåŠã³ãããš
æããŸããã ãã®ã¯ã©ã€ã¢ã³ãã®ã¢ããªã±ãŒã·ã§ã³ã³ãŒããç£æ»ãããšãã圌ã¯ã¢ããªã±ãŒã·ã§ã³ã®éçºæ¹æ³ã«é¢ããç§ã®æèŠãç¥ããããšæã£ãŠããŸããã ãã®ãããžã§ã¯ãã¯ãReactã®ã¿ã«äŸåããã®ã¯äžåçãªç¹ã«éããŸããã ãŸããã¢ããªã±ãŒã·ã§ã³ã®ç¶æ
ã管çããããã®ããè¯ãæ段ãšããŠ
ReduxãŸãã¯MobXã䜿çšããããšã«ã€ããŠ
説æããŸããã ç§ã®ã¯ã©ã€ã¢ã³ãã¯ããããã®åãã¯ãããžãŒã®ãããã¿ã€ããäœæããŸããã ãããã圌ã¯ãããã®ãã¯ãããžãŒã«çãŸãããRxJSã䜿çšããReactã¢ããªã±ãŒã·ã§ã³ã®ãããã¿ã€ããäœæããŸããã ãã®ç¬éãããç§ãã¡ã®äŒè©±ã¯ãã£ãšé¢çœããªããŸããã
åé¡ã®ã¢ããªã±ãŒã·ã§ã³ã¯ãæå·é貚ã®ååŒãã©ãããã©ãŒã ã§ããã ããŒã¿ããªã¢ã«ã¿ã€ã ã§æŽæ°ãããå€ãã®ãŠã£ãžã§ããããããŸããã ãã®ã¢ããªã±ãŒã·ã§ã³ã®éçºè
ã¯ããšãããã次ã®å°é£ãªã¿ã¹ã¯ã解決ããå¿
èŠããããŸããã
- è€æ°ã®ïŒéåæïŒããŒã¿èŠæ±ã管çããŸãã
- ã³ã³ãããŒã«ããã«äžã®å€æ°ã®ãŠã£ãžã§ããã®ãªã¢ã«ã¿ã€ã æŽæ°ã
- äžéšã®ãŠã£ãžã§ããã¯ãäžéšã®ç¹å¥ãªãœãŒã¹ããã ãã§ãªããä»ã®ãŠã£ãžã§ããããã®ããŒã¿ãå¿
èŠãšããããããŠã£ãžã§ãããšããŒã¿æ¥ç¶ã®åé¡ã®è§£æ±ºçã
ãã®çµæãéçºè
ãçŽé¢ããäž»ãªå°é£ã¯Reactã©ã€ãã©ãªèªäœãšã¯é¢ä¿ããªããããã«ããã®åéã§åœŒããå©ããããšãã§ããŸããã äž»ãªåé¡ã¯ãã·ã¹ãã ã®å
éšã¡ã«ããºã ãã€ãŸããæå·é貚ããŒã¿ãšReactããŒã«ã§äœæãããã€ã³ã¿ãŒãã§ã€ã¹èŠçŽ ããªã³ã¯ããã¡ã«ããºã ãæ£ããæ©èœãããããšã§ããã RxJSã®æ©èœãéåžžã«æçšã§ããããšãå€æããã®ã¯ãã®åéã§ããã圌ããç§ã«èŠãããããã¿ã€ãã¯éåžžã«ææã«èŠããŸããã
Reactã§ã®RxJSã®äœ¿çš
ããŒã«ã«ã¢ã¯ã·ã§ã³ãå®è¡ããåŸã
ãµãŒãããŒãã£APIã«ãªã¯ãšã¹ããéä¿¡ããã¢ããªã±ãŒã·ã§ã³ããããšã
ãŸã ã èšäºã§æ€çŽ¢ã§ããŸãã ãªã¯ãšã¹ããå®è¡ããåã«ããã®ãªã¯ãšã¹ãã圢æããããã«äœ¿çšãããããã¹ããååŸããå¿
èŠããããŸãã ç¹ã«ããã®ããã¹ãã䜿çšããŠãAPIã«ã¢ã¯ã»ã¹ããããã®URLãäœæããŸãã ãã®æ©èœãå®è£
ããReactã³ã³ããŒãã³ãã®ã³ãŒãã¯æ¬¡ã®ãšããã§ã
import React from 'react'; const App = ({ query, onChangeQuery }) => ( <div> <h1>React with RxJS</h1> <input type="text" value={query} onChange={event => onChangeQuery(event.target.value)} /> <p>{`http://hn.algolia.com/api/v1/search?query=${query}`}</p> </div> ); export default App;
ãã®ã³ã³ããŒãã³ãã«ã¯ãç¶æ
管çã·ã¹ãã ããããŸããã
query
ããããã£ã®ç¶æ
ã¯ã©ãã«ãä¿åãããŸãã
onChangeQuery
é¢æ°ãç¶æ
ãæŽæ°ããŸããã åŸæ¥ã®ã¢ãããŒãã§ã¯ããã®ãããªã³ã³ããŒãã³ãã«ã¯ããŒã«ã«ç¶æ
管çã·ã¹ãã ãè£
åãããŠããŸãã 次ã®ããã«ãªããŸãã
class App extends React.Component { constructor(props) { super(props); this.state = { query: '', }; } onChangeQuery = query => { this.setState({ query }); }; render() { return ( <div> <h1>React with RxJS</h1> <input type="text" value={this.state.query} onChange={event => this.onChangeQuery(event.target.value) } /> <p>{`http:
ãã ããããã¯ããã§èª¬æããã¢ãããŒãã§ã¯ãããŸããã 代ããã«ãRxJSã䜿çšããŠã¢ããªã±ãŒã·ã§ã³ç¶æ
管çã·ã¹ãã ã確ç«ããå¿
èŠããããŸãã é«æ¬¡ã³ã³ããŒãã³ãïŒHOCïŒã䜿çšããŠãããè¡ãæ¹æ³ãèŠãŠã¿ãŸãããã
å¿
èŠã«å¿ããŠã
App
ã³ã³ããŒãã³ãã«åæ§ã®ããžãã¯ãå®è£
ã§ããŸãããããããã¢ããªã±ãŒã·ã§ã³ã®äœæ¥ã®ããæç¹ã§ãåå©çšã«é©ãã
HOCã®åœ¢åŒã§ãã®ãããªã³ã³ããŒãã³ããèšèšããããšã決å®ããŸãã
Reactããã³RxJSããããªãŒããŒã³ã³ããŒãã³ã
ãã®ç®çã®ããã«é«æ¬¡ã®ã³ã³ããŒãã³ãã䜿çšããŠãRxJSã䜿çšããŠReactã¢ããªã±ãŒã·ã§ã³ã®ç¶æ
ã管çããæ¹æ³ãèŠã€ããŸãã 代ããã«ããå°éå
·ã®ã¬ã³ããªã³ã°ã
ãã³ãã¬ãŒããå®è£
ã§ããŸãã ãã®ããããã®ç®çã®ããã«é«æ¬¡ã³ã³ããŒãã³ããèªåã§äœæããããªãå Žåã¯ã
mapPropsStream()
ããã³
componentFromStream()
芳å¯å¯èœãªé«æ¬¡ã³ã³ããŒãã³ã
mapPropsStream()
ã䜿çšã§ããŸãã ãã ãããã®ã¬ã€ãã§ã¯ããã¹ãŠãèªåã§è¡ããŸãã
import React from 'react'; const withObservableStream = (...) => Component => { return class extends React.Component { componentDidMount() {} componentWillUnmount() {} render() { return ( <Component {...this.props} {...this.state} /> ); } }; }; const App = ({ query, onChangeQuery }) => ( <div> <h1>React with RxJS</h1> <input type="text" value={query} onChange={event => onChangeQuery(event.target.value)} /> <p>{`http://hn.algolia.com/api/v1/search?query=${query}`}</p> </div> ); export default withObservableStream(...)(App);
äžæ¹ãé«æ¬¡ã³ã³ããŒãã³ãRxJSã¯ã¢ã¯ã·ã§ã³ãå®è¡ããŸããã ç¬èªã®ç¶æ
ãšããããã£ã®ã¿ãå
¥åã³ã³ããŒãã³ãã«è»¢éããŸããå
¥åã³ã³ããŒãã³ãã¯ããã®å©ããåããŠæ¡åŒµãããäºå®ã§ãã ã芧ã®ãšãããæçµçã«ã¯ãReactç¶æ
ã®ç®¡çã«é«æ¬¡ã®ã³ã³ããŒãã³ããé¢äžããŸãã ãã ãããã®ç¶æ
ã¯ã芳枬ããããããŒããååŸãããŸãã HOCã®å®è£
ãš
App
ã³ã³ããŒãã³ãã§ã®äœ¿çšãéå§ããåã«ãRxJSãã€ã³ã¹ããŒã«ããå¿
èŠããããŸãã
npm install rxjs
ããã§ã¯ãé«æ¬¡ã³ã³ããŒãã³ãã®äœ¿çšãéå§ããŠããã®ããžãã¯ãå®è£
ããŸãããã
import React from 'react'; import { BehaviorSubject } from 'rxjs'; ... const App = ({ query, onChangeQuery }) => ( <div> <h1>React with RxJS</h1> <input type="text" value={query} onChange={event => onChangeQuery(event.target.value)} /> <p>{`http://hn.algolia.com/api/v1/search?query=${query}`}</p> </div> ); const query$ = new BehaviorSubject({ query: 'react' }); export default withObservableStream( query$, { onChangeQuery: value => query$.next({ query: value }), } )(App);
App
ã³ã³ããŒãã³ãèªäœã¯å€æŽãããŸããã 2ã€ã®åŒæ°ãé«æ¬¡ã³ã³ããŒãã³ãã«æž¡ããŸããã ãããã«ã€ããŠèª¬æããŸãã
- 芳枬ããããªããžã§ã¯ãã
query
åŒæ°ã¯ãåæå€ãæã€èŠ³æž¬å¯èœãªãªããžã§ã¯ãã§ãããæéã®çµéãšãšãã«æ°ããå€ãè¿ããŸãïŒããã¯BehaviorSubject
ïŒã 誰ã§ããã®ç£èŠå¯èœãªããžã§ã¯ãã«ãµãã¹ã¯ã©ã€ãã§ããŸãã BehaviorSubject
åã®ãªããžã§ã¯ãã«ã€ããŠRxJSã®ããã¥ã¡ã³ããè¿°ã¹ãŠããã®ã¯ãã Subject
ãªããžã§ã¯ãã®ãªãã·ã§ã³ã®1ã€ã¯ããçŸåšã®å€ãã®æŠå¿µã䜿çšããBehaviorSubject
ã§ãã ãµãã¹ã¯ã©ã€ãã«æž¡ãããæåŸã®å€ãä¿åããæ°ãããªãã¶ãŒãããµãã¹ã¯ã©ã€ããããšãããã«BehaviorSubject
ãããã®ãçŸåšã®å€ããåãåããŸãã ãã®ãããªãªããžã§ã¯ãã¯ãæ°ããéšåãæéãšãšãã«è¡šç€ºãããããŒã¿ã®è¡šç€ºã«é©ããŠããŸããã - ç£èŠå¯Ÿè±¡ãªããžã§ã¯ãã«æ°ããå€ãçºè¡ããããã®ã·ã¹ãã ïŒããªã¬ãŒïŒã HOCãä»ããŠ
App
ã³ã³ããŒãã³ãã«æž¡ãããonChangeQuery()
é¢æ°ã¯ã onChangeQuery()
察象ãªããžã§ã¯ãã«æ¬¡ã®å€ãæž¡ãéåžžã®é¢æ°ã§ãã ãã®é¢æ°ã¯ã芳枬ããããªããžã§ã¯ãã§ç¹å®ã®ã¢ã¯ã·ã§ã³ãå®è¡ããããã€ãã®é¢æ°ãé«æ¬¡ã³ã³ããŒãã³ãã«è»¢éããå¿
èŠãããå Žåãããããããªããžã§ã¯ãã§è»¢éãããŸãã
ç£èŠå¯Ÿè±¡ãªããžã§ã¯ããäœæããŠãµãã¹ã¯ã©ã€ããããšããªã¯ãšã¹ãã®ãããŒãæ©èœããã¯ãã§ãã ãã ãããããŸã§ã¯ãé«æ¬¡ã³ã³ããŒãã³ãèªäœã¯ãã©ãã¯ããã¯ã¹ã®ããã«èŠããŸãã ç§ãã¡ã¯ãããå®çŸããŸãïŒ
const withObservableStream = (observable, triggers) => Component => { return class extends React.Component { componentDidMount() { this.subscription = observable.subscribe(newState => this.setState({ ...newState }), ); } componentWillUnmount() { this.subscription.unsubscribe(); } render() { return ( <Component {...this.props} {...this.state} {...triggers} /> ); } }; };
é«æ¬¡ã³ã³ããŒãã³ãã¯ããªãã¶ãŒããã«ãªããžã§ã¯ããšããªã¬ãŒãæã€ãªããžã§ã¯ãïŒé¢æ°ãå«ããã®ãªããžã§ã¯ãã¯ãRxJSã¬ãã·ã³ã³ããããæåããçšèªãšåŒã°ããå¯èœæ§ããããŸãïŒãåãåããŸãã
ããªã¬ãŒã¯ãHOCãä»ããŠå
¥åã³ã³ããŒãã³ãã«ã®ã¿æž¡ãããŸãã ãã®ããã
App
ã³ã³ããŒãã³ãã¯
onChangeQuery()
é¢æ°ãçŽæ¥åãåããŸãããã®é¢æ°ã¯ã
onChangeQuery()
察象ãªããžã§ã¯ããšçŽæ¥é£æºããŠãæ°ããå€ãæž¡ããŸãã
ç£èŠå¯Ÿè±¡ãªããžã§ã¯ãã¯ã
componentDidMount()
ã©ã€ããµã€ã¯ã«ã¡ãœããã䜿çšããŠçœ²åãã
componentDidMount()
ã¡ãœããã䜿çšããŠç»é²è§£é€ããŸãã
ã¡ã¢ãªãªãŒã¯ãé²ãã«ã¯ãç»é²è§£é€ãå¿
èŠã§ãã ãªãã¶ãŒããã«ãªããžã§ã¯ãã®ãµãã¹ã¯ãªãã·ã§ã³ã§ã¯ãé¢æ°ã¯
this.setState()
ã³ãã³ãã䜿çšããŠãã¹ããªãŒã ããã®ãã¹ãŠã®çä¿¡ããŒã¿ãããŒã«ã«ã®Reactç¶æ
ã¹ãã¢ã«éä¿¡ããã ãã§ãã
App
ã³ã³ããŒãã³ãã«å°ããªå€æŽãå ããŸããããããã«ãããé«æ¬¡ã³ã³ããŒãã³ãã
query
ããããã£ã®åæå€ãèšå®ããªãå Žåã«çºçããåé¡ãåãé€ããŸãã ãããè¡ãããªãå Žåãäœæ¥ã®éå§æã«ã
query
ããããã£ã¯
undefined
ãŸãã ãã®å€æŽã«ããããã®ããããã£ã¯ããã©ã«ãå€ãååŸããŸãã
const App = ({ query = '', onChangeQuery }) => ( <div> <h1>React with RxJS</h1> <input type="text" value={query} onChange={event => onChangeQuery(event.target.value)} /> <p>{`http://hn.algolia.com/api/v1/search?query=${query}`}</p> </div> );
ãã®åé¡ã«å¯ŸåŠããå¥ã®æ¹æ³ã¯ãé«æ¬¡ã³ã³ããŒãã³ãã§
query
ã®åæç¶æ
ãèšå®ããããšã§ãã
const withObservableStream = ( observable, triggers, initialState, ) => Component => { return class extends React.Component { constructor(props) { super(props); this.state = { ...initialState, }; } componentDidMount() { this.subscription = observable.subscribe(newState => this.setState({ ...newState }), ); } componentWillUnmount() { this.subscription.unsubscribe(); } render() { return ( <Component {...this.props} {...this.state} {...triggers} /> ); } }; }; const App = ({ query, onChangeQuery }) => ( ... ); export default withObservableStream( query$, { onChangeQuery: value => query$.next({ query: value }), }, { query: '', } )(App);
ãã®ã¢ããªã±ãŒã·ã§ã³ãä»ããè©ŠããŠã¿ããšãå
¥åããã¯ã¹ã¯æåŸ
ã©ããã«åäœããã¯ãã§ãã
App
ã³ã³ããŒãã³ãã¯ãããããã£ã®åœ¢åŒã§HOCãã
query
ç¶æ
ãšç¶æ
ãå€æŽããããã®
onChangeQuery
é¢æ°ã®ã¿ã
onChangeQuery
ãŸãã
å
éšReactç¶æ
ã¹ãã¢ãé«æ¬¡ã³ã³ããŒãã³ãå
ã§äœ¿çšããããšããäºå®ã«ãããããããç¶æ
ã®ååŸãšå€æŽã¯RxJSç£èŠå¯èœãªããžã§ã¯ããä»ããŠè¡ãããŸãã ç£èŠå¯Ÿè±¡ãªããžã§ã¯ãã®ãµãã¹ã¯ãªãã·ã§ã³ããæ¡åŒµã³ã³ããŒãã³ãïŒ
App
ïŒã®ããããã£ã«çŽæ¥ããŒã¿ãã¹ããªãŒãã³ã°ããåé¡ã®æãããªè§£æ±ºçãèŠã€ãããŸããã§ããã ãã®ãããReactã®ããŒã«ã«ç¶æ
ãäžéå±€ã®åœ¢ã§äœ¿çšããå¿
èŠããããŸãããããã¯ãããã«ãåã¬ã³ããªã³ã°ã®åå ãšãªãç¹ã§äŸ¿å©ã§ãã åãç®æšãéæããå¥ã®æ¹æ³ãç¥ã£ãŠããå Žåã¯ãã³ã¡ã³ãã§å
±æã§ããŸãã
Reactã§èŠ³æž¬ããããªããžã§ã¯ããçµåãã
query
ããããã£ã®ããã«ã
App
ã³ã³ããŒãã³ãã§äœ¿çšã§ããå€ã®2çªç®ã®ã¹ããªãŒã ãäœæããŸãããã åŸã§äž¡æ¹ã®å€ã䜿çšããå¥ã®ç£èŠå¯èœãªãªããžã§ã¯ãã䜿çšããŠããããæäœããŸãã
const SUBJECT = { POPULARITY: 'search', DATE: 'search_by_date', }; const App = ({ query = '', subject, onChangeQuery, onSelectSubject, }) => ( <div> <h1>React with RxJS</h1> <input type="text" value={query} onChange={event => onChangeQuery(event.target.value)} /> <div> {Object.values(SUBJECT).map(value => ( <button key={value} onClick={() => onSelectSubject(value)} type="button" > {value} </button> ))} </div> <p>{`http://hn.algolia.com/api/v1/${subject}?query=${query}`}</p> </div> );
ã芧ã®ãšãããAPIã«ã¢ã¯ã»ã¹ããããã«äœ¿çšãããURLãäœæãããšãã«ã
subject
ãã©ã¡ãŒã¿ãŒã䜿çšããŠèŠæ±ãçµã蟌ãããšãã§ããŸãã ããªãã¡ãè³æã¯ããã®äººæ°ãŸãã¯çºè¡æ¥ã«åºã¥ããŠæ€çŽ¢ã§ããŸãã 次ã«ã
subject
ãã©ã¡ãŒã¿ãŒã®å€æŽã«äœ¿çšã§ããå¥ã®ç£èŠå¯èœãªãªããžã§ã¯ããäœæããŸãã ãã®ãªãã¶ãŒããã«ã䜿çšããŠã
App
ã³ã³ããŒãã³ããããé«æ¬¡ã®ã³ã³ããŒãã³ãã«é¢é£ä»ããããšãã§ããŸãã ããããªããšã
App
ã³ã³ããŒãã³ãã«æž¡ãããããããã£ãæ©èœããŸããã
import React from 'react'; import { BehaviorSubject, combineLatest } from 'rxjs/index'; ... const query$ = new BehaviorSubject({ query: 'react' }); const subject$ = new BehaviorSubject(SUBJECT.POPULARITY); export default withObservableStream( combineLatest(subject$, query$, (subject, query) => ({ subject, query, })), { onChangeQuery: value => query$.next({ query: value }), onSelectSubject: subject => subject$.next(subject), }, )(App);
onSelectSubject()
ããªã¬ãŒã¯æ°ãããã®ã§ã¯ãããŸããã ãã¿ã³ãä»ããŠã2ã€ã®
subject
ç¶æ
ãåãæ¿ããããã«äœ¿çšã§ããŸãã ãããã芳枬ããããªããžã§ã¯ãã¯ãé«æ¬¡ã®ã³ã³ããŒãã³ãã«æž¡ãããæ°ãããã®ã§ãã
combineLatest()
é¢æ°ã䜿çšããŠã2ã€ïŒãŸãã¯ãã以äžïŒã®ç£èŠã¹ã¬ããããæåŸã«è¿ãããå€ãçµåããŸãã ç£èŠå¯Ÿè±¡ãªããžã§ã¯ãããµãã¹ã¯ã©ã€ãããåŸãå€ïŒ
query
ãŸãã¯
subject
ïŒã®ãããããå€æŽããããšããµãã¹ã¯ã©ã€ããŒã¯äž¡æ¹ã®å€ãåãåããŸãã
combineLatest()
é¢æ°ã«ãã£ãŠå®è£
ãããã¡ã«ããºã ãè£å®ãããã®ãæåŸã®åŒæ°ã§ãã ããã§ãç£èŠå¯Ÿè±¡ãªããžã§ã¯ãã«ãã£ãŠçæãããå€ã®æ»ãé åºãèšå®ã§ããŸãã ãã®å Žåããªããžã§ã¯ããšããŠè¡šçŸããå¿
èŠããããŸãã ããã«ããã以åãšåæ§ã«ãããããé«æ¬¡ã®ã³ã³ããŒãã³ãã§å解ããReactã®ããŒã«ã«ç¶æ
ã«æžã蟌ãããšãã§ããŸãã å¿
èŠãªæ§é ãæ¢ã«ããã®ã§ã芳枬ããã
query
ãªããžã§ã¯ãã®ãªããžã§ã¯ããã©ããããã¹ããããçç¥ã§ããŸãã
... const query$ = new BehaviorSubject('react'); const subject$ = new BehaviorSubject(SUBJECT.POPULARITY); export default withObservableStream( combineLatest(subject$, query$, (subject, query) => ({ subject, query, })), { onChangeQuery: value => query$.next(value), onSelectSubject: subject => subject$.next(subject), }, )(App);
ãœãŒã¹ãªããžã§ã¯ã
{ query: '', subject: 'search' }
ãç£èŠå¯Ÿè±¡ãªããžã§ã¯ãã®çµåã¹ããªãŒã ã«ãã£ãŠè¿ãããä»ã®ãã¹ãŠã®ãªããžã§ã¯ãã¯ãããããé«æ¬¡ã³ã³ããŒãã³ãã§å解ãã察å¿ããå€ãããŒã«ã«ã®Reactç¶æ
ã«æžã蟌ãã®ã«é©ããŠããŸãã åãšåæ§ã«ç¶æ
ãæŽæ°ããåŸãã¬ã³ããªã³ã°ãå®è¡ãããŸãã æŽæ°ãããã¢ããªã±ãŒã·ã§ã³ãèµ·åãããšãå
¥åãã£ãŒã«ããšãã¿ã³ã䜿çšããŠäž¡æ¹ã®å€ãå€æŽã§ããã¯ãã§ãã å€æŽãããå€ã¯ãAPIãžã®ã¢ã¯ã»ã¹ã«äœ¿çšãããURLã«åœ±é¿ããŸãã ãããã®å€ã®1ã€ã®ã¿ãå€æŽãããå Žåã§ãã
combineLatest()
é¢æ°ã¯åžžã«èŠ³æž¬ããããããŒããçºè¡ãããææ°ã®å€ãçµåãããããä»ã®å€ã¯ãã®æåŸã®ç¶æ
ãä¿æããŸãã
Reactã§ã®AxiosãšRxJS
ã·ã¹ãã ã§ã¯ãAPIã«ã¢ã¯ã»ã¹ããããã®URLã¯ãä»ã®2ã€ã®ãªãã¶ãŒããã«ãªããžã§ã¯ããå«ãçµåãªãã¶ãŒããã«ãªããžã§ã¯ãããã®2ã€ã®å€ã«åºã¥ããŠæ§ç¯ãããŸãã ãã®ã»ã¯ã·ã§ã³ã§ã¯ãURLã䜿çšããŠAPIããããŒã¿ãããŒãããŸãã
ReactããŒã¿èªã¿èŸŒã¿ã·ã¹ãã ã®äœ¿çšã¯åŸæãããããŸããããRxJSãªãã¶ãŒããã«ã䜿çšããå Žåã¯ãã¢ããªã±ãŒã·ã§ã³ã«å¥ã®ãªãã¶ãŒããã«ã¹ããªãŒã ãè¿œå ããå¿
èŠããããŸãã
次ã®
ç£èŠå¯Ÿè±¡ãªããžã§ã¯ãã®äœæ¥ã«
åããããåã«ã
axiosãã€ã³ã¹ããŒã«ããŸãã ããã¯ãã¹ããªãŒã ããããã°ã©ã ã«ããŒã¿ãããŒãããããã«äœ¿çšããã©ã€ãã©ãªã§ãã
npm install axios
ããã§ã
App
ã³ã³ããŒãã³ããåºåããèšäºã®é
åããããšæ³åããŠãã ããã ããã§ã¯ãããã©ã«ãã§å¯Ÿå¿ãããã©ã¡ãŒã¿ãŒã®å€ãšããŠã空ã®é
åã䜿çšããŸããããã¯ãä»ã®ãã©ã¡ãŒã¿ãŒã§æ¢ã«è¡ã£ãã®ãšåãããšã§ãã
... const App = ({ query = '', subject, stories = [], onChangeQuery, onSelectSubject, }) => ( <div> ... <p>{`http://hn.algolia.com/api/v1/${subject}?query=${query}`}</p> <ul> {stories.map(story => ( <li key={story.objectID}> <a href={story.url || story.story_url}> {story.title || story.story_title} </a> </li> ))} </ul> </div> );
ãªã¹ãå
ã®åèšäºã§ã¯ãã¢ã¯ã»ã¹ããŠããAPIãç°çš®ã§ãããšããäºå®ã®ããã«ã代æ¿å€ã®äœ¿çšãæäŸãããŠããŸãã çŸåšãæãèå³æ·±ãã®ã¯ãæ°ãããªãã¶ãŒããã«ãªããžã§ã¯ãã®å®è£
ã§ãããã®ãªããžã§ã¯ãã¯ãããŒã¿ãèŠèŠåããReactã¢ããªã±ãŒã·ã§ã³ã«ããŒã¿ãããŒããã圹å²ãæãããŸãã
import React from 'react'; import axios from 'axios'; import { BehaviorSubject, combineLatest } from 'rxjs'; import { flatMap, map } from 'rxjs/operators'; ... const query$ = new BehaviorSubject('react'); const subject$ = new BehaviorSubject(SUBJECT.POPULARITY); const fetch$ = combineLatest(subject$, query$).pipe( flatMap(([subject, query]) => axios(`http://hn.algolia.com/api/v1/${subject}?query=${query}`), ), map(result => result.data.hits), ); ...
æ°ãããªãã¶ãŒããã«ãªããžã§ã¯ãã¯ã
subject
ãš
query
ãªãã¶ãŒããã«ã®çµã¿åããã§ãããªããªããAPIã䜿çšããŠããŒã¿ãèªã¿èŸŒãURLãäœæããã«ã¯ãäž¡æ¹ã®å€ãå¿
èŠã ããã§ãã ç£èŠå¯Ÿè±¡ãªããžã§ã¯ãã®
pipe()
ã¡ãœããã§ã¯ãå€ã䜿çšããŠç¹å®ã®ã¢ã¯ã·ã§ã³ãå®è¡ããããã«ããããããRxJSæŒç®åãã䜿çšã§ããŸãã ãã®å Žåãã¯ãšãªã«é
眮ããã2ã€ã®å€ããããã³ã°ããaxiosãçµæãååŸããããã«äœ¿çšããŸãã ããã§ã¯ã
flatMap()
ã§ã¯ãªã
flatMap()
æŒç®åã䜿çšããŠãè¿ãããpromiseèªäœã§ã¯ãªããæ£åžžã«è§£æ±ºãããpromiseã®çµæã«ã¢ã¯ã»ã¹ããŸãã ãã®çµæããã®æ°ãããªãã¶ãŒããã«ãªããžã§ã¯ãã«ãµãã¹ã¯ã©ã€ãããåŸãä»ã®ãªãã¶ãŒããã«ãªããžã§ã¯ãããæ°ãã
subject
ãŸãã¯
query
å€ãã·ã¹ãã ã«å
¥åããããã³ã«ãæ°ãã
query
ãå®è¡ãããçµæããµãã¹ã¯ãªãã·ã§ã³é¢æ°ã«ãªããŸãã
ããã§ãåã³ãé«æ¬¡ã³ã³ããŒãã³ãã«æ°ãããªãã¶ãŒããã«ãªããžã§ã¯ããæäŸã§ããŸãã ç§ãã¡ã¯
combineLatest()
é¢æ°ã®æåŸã®åŒæ°ãæã£ãŠããŸããããã«ãããããã
stories
ãšããããããã£ã«çŽæ¥ãããã³ã°ããããšãã§ããŸãã æçµçã«ãããã¯ããã®ããŒã¿ã
App
ã³ã³ããŒãã³ãã§æ¢ã«ã©ã®ããã«äœ¿çšãããŠããããè¡šããŠããŸãã
export default withObservableStream( combineLatest( subject$, query$, fetch$, (subject, query, stories) => ({ subject, query, stories, }), ), { onChangeQuery: value => query$.next(value), onSelectSubject: subject => subject$.next(subject), }, )(App)
ç£èŠå¯Ÿè±¡ãªããžã§ã¯ãã¯ãä»ã®2ã€ã®ç£èŠãããŒã«ãã£ãŠéæ¥çã«ã¢ã¯ãã£ãåããããããããã§ã¯ããªã¬ãŒã¯ãããŸããã å
¥åãã£ãŒã«ãïŒ
query
ïŒã®å€ãå€æŽããããããã¿ã³ïŒ
subject
ïŒãã¯ãªãã¯ããããã³ã«ãããã¯äž¡æ¹ã®ã¹ããªãŒã ããã®ææ°ã®å€ãå«ã
fetch
察象
fetch
ãªããžã§ã¯ãã«åœ±é¿ããŸãã
ãã ããå
¥åãã£ãŒã«ãã®å€ãå€ãããã³ã«ã
fetch
察象ã®
fetch
ãªããžã§ã¯ãã«åœ±é¿ãäžããå¿
èŠã¯ãªãã§ãããã ããã«ãå€ã空ã®æååã§è¡šãããå Žåã
fetch
ã«åœ±é¿ãäžããããªãã§ãããã ãã®ããã
debounce
ã¹ããŒãã¡ã³ãã䜿çšããŠã
debounce
å¯èœãª
query
ãªããžã§ã¯ããæ¡åŒµã§ã
query
ãããã«ãããã¯ãšãªã®é »ç¹ãªå€æŽãäžèŠã«ãªããŸãã ã€ãŸãããã®ã¡ã«ããºã ã®ãããã§ãæ°ããã€ãã³ãã¯ãåã®ã€ãã³ãããæå®ã®æéãçµéããåŸã«ã®ã¿åãå
¥ããããŸãã ããã«ãããã§
filter
æŒç®åã䜿çšã
filter
ãããã¯ã
query
æååã空ã®å Žåã«ã¹ããªãŒã ã€ãã³ãããã£ã«ã¿ãŒã§é€å€ããŸãã
import React from 'react'; import axios from 'axios'; import { BehaviorSubject, combineLatest, timer } from 'rxjs'; import { flatMap, map, debounce, filter } from 'rxjs/operators'; ... const queryForFetch$ = query$.pipe( debounce(() => timer(1000)), filter(query => query !== ''), ); const fetch$ = combineLatest(subject$, queryForFetch$).pipe( flatMap(([subject, query]) => axios(`http://hn.algolia.com/api/v1/${subject}?query=${query}`), ), map(result => result.data.hits), ); ...
debounce
ã¹ããŒãã¡ã³ãã¯ããã£ãŒã«ãã«ããŒã¿ãå
¥åãããžã§ããå®è¡ããŸãã ãã ãããã¿ã³ã
subject
å€ãã¯ãªãã¯ãã
subject
ããªã¯ãšã¹ãã¯ããã«å®è¡ãããŸãã
ããã§ã
App
ã³ã³ããŒãã³ããåããŠè¡šç€ºãããšãã«è¡šç€ºããã
query
ããã³
subject
ã®åæå€ã¯ãç£èŠå¯Ÿè±¡ãªããžã§ã¯ãã®åæå€ããååŸãããã®ãšåãã§ã¯ãããŸããã
const query$ = new BehaviorSubject('react'); const subject$ = new BehaviorSubject(SUBJECT.POPULARITY);
subject
ã«
subject
èšè¿°ããã
query
ã¯ç©ºã®æååãå«ãŸã
query
ã ããã¯ã眲åå
ã®
App
ã³ã³ããŒãã³ãã®æ©èœãå解ããããã®ããã©ã«ãã®ãã©ã¡ãŒã¿ãŒãšããŠæäŸããã®ã¯ãããã®å€ã§ãã£ãããã§ãã ããã¯ã
fetch
察象ã®
fetch
ãªããžã§ã¯ãã«ãã£ãŠå®è¡ãããæåã®èŠæ±ãåŸ
ã€å¿
èŠãããããã§ãã ããŒã«ã«ç¶æ
ã«æžã蟌ãããã«ãé«æ¬¡ã³ã³ããŒãã³ãã®ãªãã¶ãŒããã«
query
ããã³
subject
ãªããžã§ã¯ãããå€ãããã«ååŸããæ¹æ³ãæ£ç¢ºã«ã¯ããããªããããé«æ¬¡ã³ã³ããŒãã³ãã®åæç¶æ
ãå床èšå®ããããšã«ããŸããã
const withObservableStream = ( observable, triggers, initialState, ) => Component => { return class extends React.Component { constructor(props) { super(props); this.state = { ...initialState, }; } componentDidMount() { this.subscription = observable.subscribe(newState => this.setState({ ...newState }), ); } componentWillUnmount() { this.subscription.unsubscribe(); } render() { return ( <Component {...this.props} {...this.state} {...triggers} /> ); } }; };
ããã§ãåæç¶æ
ã3çªç®ã®åŒæ°ãšããŠé«æ¬¡ã³ã³ããŒãã³ãã«æäŸã§ããŸãã å°æ¥çã«ã¯ã
App
ã³ã³ããŒãã³ãã®ããã©ã«ãèšå®ãåé€ã§ããŸãã
... const App = ({ query, subject, stories, onChangeQuery, onSelectSubject, }) => ( ... ); export default withObservableStream( combineLatest( subject$, query$, fetch$, (subject, query, stories) => ({ subject, query, stories, }), ), { onSelectSubject: subject => subject$.next(subject), onChangeQuery: value => query$.next(value), }, { query: 'react', subject: SUBJECT.POPULARITY, stories: [], }, )(App);
ç§ãä»å¿é
ããŠããã®ã¯ãç£èŠå¯Ÿè±¡ãªããžã§ã¯ã
query$
ããã³
subject$
ã®å®£èšã«ãåæç¶æ
ãèšå®ãããŠããããšã§ãã 芳枬ããããªããžã§ã¯ãã®åæåãšé«æ¬¡ã³ã³ããŒãã³ãã®åæç¶æ
ã¯åãå€ãå
±æããããããã®ã¢ãããŒãã¯ãšã©ãŒãèµ·ãããããã§ãã 代ããã«ãåæç¶æ
ãèšå®ããããã«ãé«æ¬¡ã®ã³ã³ããŒãã³ãã§èŠ³æž¬ããããªããžã§ã¯ãããåæå€ãååŸããæ¹ãããã£ãã§ãããã ããããããã®è³æã®èªè
ã®1人ãããããè¡ãæ¹æ³ã«é¢ããã³ã¡ã³ãã§ã¢ããã€ã¹ãå
±æã§ããã§ãããã
ããã§è¡ã£ããœãããŠã§ã¢ãããžã§ã¯ãã®ã³ãŒãã¯ãããã«ãã
ãŸã ã
ãŸãšã
ãã®èšäºã®äž»ãªç®æšã¯ãRxJSã䜿çšããŠReactã¢ããªã±ãŒã·ã§ã³ãéçºããããã®ä»£æ¿ã¢ãããŒãã瀺ãããšã§ãã 圌ãããªãã«é£ã¹ç©ãäžããŠãããããšãé¡ã£ãŠããŸãã ReduxãšMobXã¯å¿
èŠãªãå ŽåããããŸããããã®ãããªç¶æ³ã§ã¯ãRxJSãç¹å®ã®ãããžã§ã¯ãã«é©ãããã®ã«ãªãã§ãããã
芪æãªãèªè
ïŒ Reactã¢ããªã±ãŒã·ã§ã³ãéçºãããšãã«RxJSã䜿çšããŠããŸããïŒ
