Pro Yearly is on sale from $80 to $50! »

Get Started React.js

45daf58c77e9dbbab5a1c8a5afc7ac5c?s=47 koba04
September 30, 2014

Get Started React.js

React.jsについての簡単な紹介
Component化、Prop、Stateの使い分け、イベント、addon、mixin、アーキテクチャ、ServerSide Renderingなどについての説明。
一部v0.12で変わった部分の対応もしています。

45daf58c77e9dbbab5a1c8a5afc7ac5c?s=128

koba04

September 30, 2014
Tweet

Transcript

  1. (FU4UBSUFE3FBDUKT !LPCB WରԠ

  2. facebook.github.io/react/

  3. 3FBDUKT W w +6455)&6* w .7$Ͱ͍͏ͱ͜Ζͷ7 㱠.7$'SBNFXPSL  w 7*356"-%0.

    w Ծ૝ͷ%0.Λ͍࣋ͬͯͯɺࠩ෼͚ͩΛ࣮ࡍͷ%0.ʹ൓ө͢ΔͷͰߴ଎ w %"5"'-08 w XBZσʔλόΠϯσΟϯάͰͳͯ͘ํ޲ͷϦΞΫςΟϒͳσʔλϑϩʔ w +49 w +BWB4DSJQU 9.-MJLFͳهड़ PQUJPOBM
  4. http://blog.atom.io/2014/07/02/moving-atom-to-react.html

  5. )FMMP3FBDUKT

  6. )FMMP3FBDU /** @jsx React.DOM */ // ComponentΛ࡞੒ var App =

    React.createClass({ render: function() { return ( <p>Hello React</p> ); } }); // DOMͱ<h1><App /></h1>ͱ͍͏componentΛඥ෇͚Δ React.render( <h1><App /></h1>, document.getElementById('app') );
  7. )FMMP+49 w )5.-ͷEJWཁૉʹݟ͑Δ͚ͲɺEJW΋3FBDUͰఆٛ͞Εͨ $PNQPOFOU w ʮDMBTTlIPHFzʯͰ͸ͳͯ͘ʮDMBTT/BNFlIPHFzʯ render() { var style

    = { backgroundColor: ‘white’, }; return ( <div> <header className=“page-header”> <span style={style}>xxx</span> </header> </div> ); renderͰฦ͢component͸1ͭ
  8. 4UBSUXJUI+49 w +495SBOTGPSNFSΛ࢖ͬͯΦϯϥΠϯͰม׵ w SFBDUUPPMTΛ࢖ͬͯDPNNBOEMJOFͰม׵ <script src="build/react.js"></script> <script src="build/JSXTransformer.js"></script> %

    npm install -g react-tools % jsx --watch src/ build/ # require͢ΔͳͲͯ͠React͕scopeͷதͰݟ͑Δඞཁ͕͋Δ var React = require(‘react’);
  9. 4UBSUXJUI+49 w HSVOUɺHVMQɺCSPXTFSJGZUSBOTGPSNͳͲେମ͋ΔͷͰ͓޷ΈͰ w HSVOUSFBDU w HVMQSFBDU w SFBDUJGZ w

    IUUQTHJUIVCDPNGBDFCPPLSFBDUXJLJ$PNQMFNFOUBSZ5PPMT
  10. 4UBSUXJUIPVU+49 w +BWB4DSJQUͰॻ͘͜ͱ͸ग़དྷΔ͚Ͳɺ+49Ͱॻ͍ͨ΄͏͕Θ͔Γ΍ ͍͢ͷͰΦεεϝ͸͠ͳ͍ // JavaScript React.render( React.DOM.h1(null, 'Hello, world!'),

    document.getElementById('example') ); // JSX React.render( <h1>Hello, world!</h1>, document.getElementById('example') );
  11. &4 4VQQPSU

  12. &4 4VQQPSU w IBSNPOZPQUJPO͕͋ΔͷͰ༗ޮʹͯ͠ม׵͢Δ͜ͱͰҰ෦ͷ&4  ͷGFBUVSFΛ࢖༻͢Δ͜ͱ͕ग़དྷΔ w ͜ͷลΓ͕IBSNPOZPQUJPOʹΑͬͯ࢖͑ΔͬΆ͍ 'harmony': [

    'es6-arrow-functions', 'es6-object-concise-method', 'es6-object-short-notation', 'es6-classes', 'es6-rest-params', 'es6-templates', 'es6-destructuring', 'es7-spread-property' ], https://github.com/facebook/react/blob/master/vendor/fbtransform/visitors.js#L31-L40 % jsx —harmony src/ build/
  13. &44VQQPSU w SFBDUJGZͰ΋PQUJPOΛࢦఆ͢Δ͚ͩͰ࢖͑Δ w ͦͷ݁Ռɺ͜Μͳײ͡Ͱॻ͚Δ "browserify": { "transform": [ [

    "reactify", { "harmony": true } ], ] } module.exports = React.createClass({ render() { var tracks = this.props.tracks.map( (track, index) => { return (<li className="list-group-item" key={index}> <a href={track.url} target="_blank">{track.name}</a> <span className="artist">{track.artist.name}</span> </li>); }); return (<ul className="list-group">{tracks}</ul>); } });
  14. SFBDUCPJMFSQMBUF w ͔͜͜Β঺հ͢Δίʔυ͸Լهͷߏ੒ w 5SBOTGPSN͸CSPXTFSJGZ SFBDUJGZ w IBSNPOZPQUJPOΛ࢖༻ w HJUIVCDPNLPCBSFBDUCPJMFSQMBUF

    http://koba04.github.io/react-boilerplate/
  15. $PNQPOFOU

  16. $PNQPOFOU w 7JFXΛߏ੒͢ΔύʔπͰɺ$PNQPOFOUΛ૊Έ߹ΘͤΔ͜ͱͰ7JFX Λ࡞͍ͬͯ͘ w ޙड़ͷ1SPQ΍4UBUFͰ΍ΓͱΓͯ͠$PNQPOFOUΛ࡞͍ͬͯ͘ var React = require(‘React’);

    var App = React.createClass({ render: function() { return ( <p>Hello {this.props.name}</p> ); } }); React.render( <h1><App name=“React” /></h1>, document.getElementById('app') );
  17. 1SPQ

  18. 1SPQ w $PNQPOFOU͕΋ͭ*NNVUBCMFͳ1SPQFSUZ w $PNQPOFOUͷ࡞੒࣌ʹࢦఆ͢Δ͜ͱ͕ग़དྷͯɺUIJTQSPQTYYYͰ ࢀর͢Δ w QSPQTDIJMESFOͰ$PNQPOFOU಺ͷཁૉΛऔಘ͢Δ͜ͱ͕ग़དྷΔ var Sample

    = React.createClass({ render() { return ( <div> <header>{this.props.header}</header> <div className=“content”>{this.props.children}</div> </div> ); } }); React.render(<Sample header=“hello”>content</Sample>, el);
  19. 1SPQ5ZQFT

  20. 1SPQ5ZQFT w 1SPQͷ஋ʹରͯ͠ܕ΍ඞਢͳͲͷࢦఆΛ͢Δ͜ͱ͕ग़དྷΔ w ഑ྻɺจࣈྻɺ਺஋ɺΦϒδΣΫτҎ֎ʹ΋ɺ3FBDU$PNQPOFOUɺ FOVN΍ΧελϜͷ7BMJEBUPSͳͲॊೈʹࢦఆग़དྷΔ w IUUQGBDFCPPLHJUIVCJPSFBDUEPDTSFVTBCMFDPNQPOFOUTIUNM module.exports =

    React.createClass({ propTypes: { onHandleSubmit: React.PropTypes.func.isRequired, // ؔ਺Ͱඞਢ countries: React.PropTypes.array.isRequired // ഑ྻͰඞਢ },
  21. 4UBUF

  22. 4UBUF w ϢʔβʔʹΑΔૢ࡞΍"1*ϦΫΤετʹΑΓɺঢ়ଶ͕มԽ͢Δ஋ module.exports = React.createClass({ getInitialState() { // stateͷॳظԽΛߦ͏

    return { // stateͷΦϒδΣΫτΛฦ͢ artist: 'radiohead' }; }, handleInput(e) { this.setState({artist: e.target.value}); ɹɹɹɹɹɹ// stateͷ஋Λߋ৽͢Δ }, render() { return ( <div> <div>input: {this.state.artist}</div> // stateͷ஋Λࢀর͢Δ <input type="text" onChange={this.handleInput} value={this.state.artist} /> </div> ); } });
  23. 4UBUF-JOLFE4UBUF.JYJO w -JOLFE4UBUF.JYJOΛ࢖͏͜ͱʹΑΓɺXBZͳσʔλόΠϯσΟϯ ά΋؆୯ʹॻ͚Δ var React = require(‘react/addons’); // addonsΛ࢖͏৔߹͸ɺreact/addonsΛ࢖͏

    module.exports = React.createClass({ mixin: [React.addons.LinkedStateMixin], getInitialState() { return { artist: 'radiohead' }; }, render() { return ( <div> <div>input: {this.state.artist}</div> <input type="text" valueLink={this.linkState('artist')} /> </div> ); } });
  24. 1SPQ4UBUF

  25. 1SPQ4UBUF w جຊతʹ͸1SPQͰߟ͑Δ w 4UBUFʹͨ͠஋Λࢠ$PNQPOFOUʹ౉͢৔߹͸ɺ1SPQͱͯ͠౉͢ w $PNQPOFOUͷPXOFSPXOFFͷؔ܎Λҙࣝ͢Δ w ͳΔ΂͘*NNVUBCMFͳ$PNQPOFOUΛ࡞Δ͜ͱΛҙࣝ͢Δ

  26. w 1BSFOU͕ߋ৽͞ΕΔͱ$IJME΋SFSFOEFS͞ΕΔ 1SPQ4UBUF Parent state = { current: “artist”, count:

    10 } ChildB ChildA Prop = { current: “artist” } Prop = { count: 10 } pass as prop <ChildA current={this.prop.current} /> <ChildB count={this.prop.count} />
  27. $PNQPOFOU-JGFDZDMF

  28. $PNQPOFOU-JGFDZDMF w $PNQPOFOUͷ-JGFDZDMFʹԠͯ͡ϝιου͕ݺ͹ΕΔ componentWillMount() DOMπϦʔʹ௥Ճ͞ΕΔલʹҰ౓͚ͩݺ͹ΕΔͷͰॳظԽॲཧΛߦ͏ͷʹద͍ͯ͠Δ ͜ͷதͰsetStateݺΜͰ΋render࣌ʹ·ͱΊͯߦΘΕΔ ServerSide Rendering࣌ʹ΋ݺ͹ΕΔ componentDidMount() DOMπϦʔʹ௥Ճ͞Εͨঢ়ଶͰݺ͹ΕΔͷͰDOMʹؔΘΔॳظԽॲཧΛ͍ͨ͠ͱ͖ʹద͍ͯ͠Δ

    ·ͨ͸ServerSide Rendering࣌ʹݺΜͰ΄͘͠ͳ͍ॳظॲཧʹ΋ద͍ͯ͠Δ componentWillReceiveProps (nextProps) Prop͕ߋ৽͞ΕΔ࣌ʹݺ͹ΕΔ this.props͸ݹ͍஋Ͱ৽͍͠Prop͸Ҿ਺Ͱ౉ͬͯ͘Δ shouldComponentUpdate (nextProps, nextState) ৽͍͠State͔PropΛड͚औͬͨࡍʹrerender͢Δ͔Ͳ͏͔ΛbooleanͰฦ͢ɻfalseʹ͢ Δͱߋ৽͠ͳ͍ɻperformaceͷվળ͍ͨ͠ͱ͖ʹ͜͜Ͱௐ੔͢Δ͜ͱ͕ग़དྷΔ componentWillUpdate (nextProps, nextState) component͕ߋ৽͞ΕΔ࣌ʹݺ͹ΕΔɻߋ৽લͷPropͱState͸Ҿ਺ͰऔಘͰ͖Δ ͜ͷதͰsetStateΛݺͿ͜ͱ͸ग़དྷͳ͍ componentDidUpdate (prevProps, prevState) component͕ߋ৽͞Εͨޙʹݺ͹ΕΔ ߋ৽લͷPropͱState͸Ҿ਺ͰऔಘͰ͖Δ componentWillUnmount() Component͕DOM͔Β࡟আ͞ΕΔͱ͖ʹݺ͹ΕΔ ΫϦʔϯΞοϓॲཧʹద͍ͯ͠Δ
  29. $PNQPOFOU-JGFDZDMF // tracks͸Backbone.Collection // tracks༻ͷmixin(ޙड़) module.exports = { componentWillMount() {

    this.tracks = tracks; tracks.on("all", this.setTracks); // ΠϕϯτΛߪಡ }, componentWillUnmount() { tracks.off("all", this.setTracks); // ΠϕϯτΛղআ }, setTracks() { this.setState({ tracks: tracks.map( (track) => { return track.attributes } ) }); }, };
  30. 'FUDIJOJUJBMEBUB

  31. 'FUDIJOJUJBMEBUB w $PNQPOFOUʹඞཁͳσʔλ͕͋Δ৔߹͸DPNQPOFOU%JE.PVOUͰ "KBY3FRVFTUΛߦ͏ͱΑ͍ w 4FSWFS4JEF3FOEFSJOH࣌ʹ΋ݺͼ͍ͨ৔߹͸8JMM.PVOUͰ w 3FTQPOTFΛTFU4UBUF͢Δͱ͖͸ɺ$PNQPOFOU͕·ͩNPVOU͞Ε ͍ͯΔ͔ΛJT.PVOUFE ͰνΣοΫͨ͠ํ͕͍͍

    componentDidMount() { this.tracks = tracks; tracks.on("all", this.setTracks); }, setTracks() { if (this.isMounted()) { this.setState({ tracks: tracks.map( (track) => { return track.attributes } ) }); } },
  32. 1BSFOUDIJMEDPNNVOJDBUJPO

  33. 1BSFOUDIJMEDPNNVOJDBUJPO w جຊతʹ͸ࢠ͕*'Λ1SPQͰެ։ͯ͠਌͕࢖͏ܗʹ͢Δ w QSPQ5ZQFTͰ੍໿Λࢦఆ͓ͯ͘͠ͱΘ͔Γ΍͍͢ var Parent = React.createClass({ search()

    { console.log(“search”); }, render() { return <div><Child onHandleClick={this.search} /></div> } }); var Child = React.createClass({ propTypes: { onHandleClick: React.PropTypes.func.isRequired, ɹ}, handleClick() { this.props.onHandleClick() }, render() { return <div onClick={this.handleClick}>click</div> } });
  34. 1BSFOUDIJMEDPNNVOJDBUJPO w UIJTIBOEMF$MJDLCJOE UIJT J ͷΑ͏ʹҾ਺Λ౉ͯ͠਌$PNQPOFOU ಺Ͱॲཧ͢Δํ๏΋͋Δ var Sample =

    React.createClass({ handleClick(i) { console.log('click' + this.props.items[i]); }, render() { var items = this.props.items.map( (item, i) => { return <div onClick={this.handleClick.bind(this, i)} key={i}>{item}</div> }); return <div>{items}</div> ); } }); <Sample items={[‘Foo’, ‘Bar’, ‘Baz’]} />
  35. 1BSFOUDIJMEDPNNVOJDBUJPO w SFGΛ࢖ͬͯࢠ$PNQPOFOUͷࢀরΛऔಘग़དྷΔ w ޙड़ͷHFU%0./PEFͱ૊Έ߹Θͤͯɺ3FBDU͕ఏڙ͍ͯ͠ΔJOQVU ͳͲͷ$PNQPOFOUʹର͢ΔࢀরΛऔಘ͢Δ͜ͱʹ࢖͏͜ͱ͸͋Δ ͚Ͳɺجຊతʹ͸ආ͚ͨํ͕͍͍ w 1SPQΛ*'ͱͨ͠ํ͕ݟ௨͕͍͍͠ w

    %0.΁ͷࢀরΛऔಘͰ͖Δ componentDidMount() { this.refs.input.getDOMNode().focus(); } render() { return ( <input type=“text” value=“{this.state.input}” ref=“input” /> ) }
  36. DIJMESFO

  37. DIJMESFO w QSPQTDIJMESFO͸഑ྻͩͬͨΓจࣈྻͩͬͨΓ͢ΔͷͰɺૢ࡞ͨ͠ Γ਺Λऔಘ͢Δͱ͖͸3FBDU$IJMESFOͷ6UJMΛ࢖͏ͱ͍͍ ͦΜͳ ʹͳ͍ͱࢥ͏͚Ͳ  w 3FBDU$IJMESFO NBQcGPS&BDIcDPVOUcPOMZ

    ͕͋Δ w \GBMTF^౉͢͜ͱͰۭͷࢠཁૉΛදݱͰ͖Δ // span componentͷ഑ྻ <div><span>xxx</span><span>xxx</span></div> // “xxx”ͱ͍͏จࣈྻ <div>xxx</div> React.Children.map(this.props.children, (child) => { … });
  38. 5SBOTGFSJOH1SPQT

  39. 5SBOTGFSJOH1SPQT w +49TQSFBEBUUSJCVUFTΛ࢖͏͔ɺ0CKFDUBTTJHOͳͲΛ࢖͏ w طଘͷ$PNQPOFOUΛ֦ு͍ͨ͠Α͏ͳ৔߹ʹ࢖͏ͱศར w ͨͩɺґଘ͍ͯ͠Δ1SPQ͕Θ͔Γʹ͘͘ͳΔͷͰ஫ҙ͕ඞཁ var Avatar =

    React.createClass({ render: function() { return ( var {userId, ...other} = this.props; <img {...other} src={“/avatars/“ + userId + ".png"} userId={null} /> ); } }); // <Avatar userId={17} width={200} height={200} />
  40. 3FBDU$PNQPOFOUT http://react-components.com/

  41. %0.&WFOU

  42. &WFOU

  43. &WFOU w Ϋϩεϒϥ΢βରԠͨ͠ωΠςΟϒ"1*ͱಉ͡*'Λ΋ͭಠࣗΠϕϯ τΛ͍࣋ͬͯΔ 4ZOUIFUJD&WFOU  w ಺෦Ͱ͸SPPUͷཁૉʹ͚ͩ&WFOU-JTUFOFSΛొ࿥ͯ͠ɺ$PNQPOFOU ͱΠϕϯτʹର͢ΔϚοϐϯάΛ࣋ͬͯॲཧ͍ͯ͠Δ w

    ΠϕϯτϋϯυϥʔͷDPOUFYU͸DPNQPOFOUͷΠϯελϯεʹͳΔ w UPVDIΠϕϯτΛ༗ޮʹ͍ͨ͠৔߹͸ɺˣͷΑ͏ʹ༗ޮʹ͢Δඞཁ ͕͋Δ // <form className="form-horizontal" role="form" onSubmit={this.handleSubmit} > handleSubmit(e) { // e͸SyntheticEvent e.preventDefault(); var artist = this.state.inputArtist; if (artist) { this.props.onHandleSubmit(artist); } },
  44. &WFOU w UPVDIΠϕϯτΛ༗ޮʹ͍ͨ͠৔߹͸ɺˣͷΑ͏ʹ༗ޮʹ͢Δඞཁ ͕͋Δ w 3FBDU͕ఏڙͯ͠ͳ͍%0.ͷΠϕϯτΛߪಡ͍ͨ͠৔߹͸ DPNQPOFOU%JE.PVOUͰBEE&WFOU-JTUFOFSͯ͠ɺ DPNQPOFOU8JMM6ONPVOUͰSFNPWF&WFOU-JTUFOFS͢Δ React.initializeTouchEvents(true); componentDidMount()

    { window.addEventListener('resize', this.handleResize); }, componentWillUnmount() { window.removeEventListener('resize', this.handleResize); },
  45. HFU%0./PEF

  46. HFU%0./PEF w $PNQPOFOU͕؅ཧ͍ͯ͠Δ%0.΁ͷࢀরΛऔಘ͢Δ͜ͱ͕ग़དྷΔ w SFGͱ૊Έ߹Θͤͯ࢖͏͜ͱ͕ଟ͍ componentDidMount() { this.refs.input.getDOMNode().focus(); } render()

    { return ( <input type=“text” value=“{this.state.input}” ref=“input” /> ) }
  47. 'PSN

  48. 'PSN$POUSPMMFE$PNQPOFOUT w WBMVFଐੑΛࢦఆ͢ΔͱɺWBMVFͷ஋ͱද্ࣔͷ஋͕ಉظ͞ΕΔͷ Ͱɺݻఆ஋Λࢦఆͨ͠৔߹͸஋͕มߋͰ͖ͳ͍ w TUBUFʹͯ͠TFU4UBUF͢Δ͜ͱͰ஋ͱද্ࣔͷ஋͕ߋ৽͞ΕΔ w IUUQKTpEEMFOFULPCBYIGQH <input type="text"

    value="never change" /> <input type="text" value={this.state.value} onChange={this.onHandleChange}/> : onHandleChange(e) { this.setState({value: e.target.value}); }
  49. 'PSN6O$POUSPMMFE$PNQPOFOUT w WBMVFଐੑΛࢦఆ͠ͳ͍৔߹͸ɺೖྗͨ͠஋͕ͦͷ··൓ө͞ΕΔɻ EFGBVMU7BMVFΛࢦఆ͢Δ͜ͱͰॳظ஋ͷઃఆ͕Մೳ w IUUQKTpEEMFOFULPCBSYK[RSW w ࢀߟ%0.ͷWBMVFͱHFU"UUSJCVUF lWBMVFz ͰऔಘͰ͖Δ஋ͷҧ͍

    w IUUQKTpEEMFOFULPCBLRVHE <input type="text" defaultValue=“initial value” onChange={this.onHandleChange} /> : onHandleChange: function(e) { this.setState({value: e.target.value}); }
  50. 'PSN5FYU"SFBBOE4FMFDU#PY w UFYUBSFB w WBMVFʹ஋Λࢦఆ͢ΔɻUFYUBSFBYYUFYUBSFBͷࢦఆΛ͢Δ ͱEFGBVMU7BMVFͱͯ͠ѻΘΕΔ w TFMFDU w WBMVFʹ஋Λࢦఆ͢ΔɻNVMUJQMFͱͯ͠഑ྻΛࢦఆग़དྷΔ

    <textarea value={this.state.value} /> <select multiple={true} value={this.state.values}> <option value=“A”>A</option> <option value=“B”>B</option> </select>
  51. .JYJOBEEPOT

  52. .JYJO

  53. .JYJO w $PNQPOFOUͷڞ௨ͷৼΔ෣͍Λ.JYJOͱͯ͠ڞ௨Խ͢Δ͜ͱ͕ग़དྷ Δ // tracks-mixin.jsx module.exports = { getInitialState()

    { return { tracks: [] }; }, componentWillMount() { this.tracks = tracks; tracks.on("all", this.setTracks); }, componentWillUnmount() { tracks.off("all", this.setTracks); }, setTracks() { this.setState({ tracks: tracks.map( (track) => { return track.attributes } ) }); }, };
  54. .JYJO w ࢖͏࣌͸NJYJOTʹࢦఆ͢Δ͚ͩ w HFU*OJUJBM4UBUFͰಉ͡LFZΛࢦఆ͢ΔͱΤϥʔʹͳΔ var TracksMixin = require(‘../tracks-mixin.jsx'); module.exports

    = React.createClass({ mixins: [TracksMixin], fetchArtist(artist) { this.tracks.fetchByArtist(artist); }, getInitialState() { return { foo: “bar”, // tracks: [] ࢦఆ͢ΔͱΤϥʔʹͳΔ }; } });
  55. BEEPOT

  56. $44$MBTT.BOJQVMBUJPO

  57. $44$MBTT.BOJQVMBUJPO w Α͋͘Δ$44ͷDMBTTΛૢ࡞͢Δ΋ͷ render() { var classes = React.addons.classSet({ ‘is-active’:

    this.props.item.isActive, ‘is-important’: this.props.item.isImportant }); return ( <div className={classes}>{this.props.item.name</div> ): }
  58. "OJNBUJPO

  59. "OJNBUJPO w "OHVMBSKTͷOHBOJNBUFʹΠϯεύΠΞ͞Ε͍ͯΔΒ͍͠ w Ξχϝʔγϣϯ͍ͤͨ͞ཁૉΛ$445SBOTJUJPO(SPVQͰғΜͰΞχ ϝʔγϣϯͷ$44Λॻ͘ // ϖʔδભҠ࣌ʹΞχϝʔγϣϯͤ͞Δ(with react-router) var

    App = React.createClass({ propTypes: { activeRouteHandler: React.PropTypes.func.isRequired }, render() { var CSSTransitionGroup = React.addons.CSSTransitionGroup; return ( <CSSTransitionGroup transitionName=“route”> // Ξχϝʔγϣϯͷ໊લ΋ࢦఆ͢Δ {this.props.activeRouteHandler()} </CSSTransitionGroup> ); } });
  60. "OJNBUJPO /* transitionName͕route (stylus) */ .route-enter -webkit-animation: fadein 0.5s -webkit-animation-delay:

    0.2s animation: fadein 0.5s animation-delay: 0.2s opacity: 0 .route-enter-active … .route-leave -webkit-animation: fadeout 0.2s animation: fadeout 0.2s .route-leave-active … @keyframes fadein 0% transform: scale(0.5) -webkit-transform: scale(0.5) opacity: 0 …
  61. "OJNBUJPO w Ξχϝʔγϣϯ։࢝࣌ʹ3FBDU$445SBOTJUJPO(SPVQ͸Ϛ΢ϯτ͞ Ε͍ͯΔඞཁ͕͋Δ w Ξχϝʔγϣϯର৅ͷཁૉʹ͸LFZଐੑΛ͚ͭΔඞཁ͕͋Δ w USBOTJUJPO &OUFSc-FBWF ʹΑͬͯFOUFS

    MFBWFͷͲͪΒ͔ͷΈΞχ ϝʔγϣϯͤ͞Δ͜ͱ΋ग़དྷΔ w ࡉ੍͔͍ޚΛ͍ͨ͠৔߹͸ɺ3FBDU5SBOTJUJPO(SPVQΛ࢖͏ <CSSTransitionGroup transitionName="example"> <div key={item}>{item}</div> </CSSTransitionGroup> <CSSTransitionGroup transitionName=“example" transitionLeave={false}>
  62. 5FTU

  63. 5FTU w 3FBDUBEEPOT5FTU6UJMTͱͯ͠6UJMܥ͕αϙʔτ͞Ε͍ͯΔ w 4JNVMBUPS͕ศརͦ͏ w IUUQGBDFCPPLHJUIVCJPSFBDUEPDTUFTUVUJMTIUNM w ࣮ࡍɺࢼͯ͠ͳ͍͚Ͳʜ var

    node = this.refs.input.getDOMNode(); React.addons.TestUtils.Simulate.click(node); React.addons.TestUtils.Simulate.change(node); React.addons.TestUtils.Simulate.keyDown(node, {key: "Enter"});
  64. 1FSGPSNBODF

  65. 1FSGPSNBODF w 7JSUVBM%0.ͷEJ⒎Λܭࢉͯͦ͠ͷ෦෼͚ͩΛ࣮ࡍͷ%0.ʹ൓ө͠ ͯ͘ΕΔͷͰύϑΥʔϚϯεʹ༏Ε͍ͯΔ w ίʔυΛॻ͘ͱ͖ʹ%0.ؔ࿈ͷύϑΥʔϚϯεΛ͋·Γҙࣝͨ͘͠ ͳ͍ਓʹͱͬͯ΋3FBDUKTʹ೚͓ͤͯ͘͜ͱ͕ग़དྷΔͷͰΑͦ͞͏ w γϏΞʹύϑΥʔϚϯε͕ٻΊΒΕΔΑ͏ͳ৔߹͸ɺ TIPVME$PNQPOFOU6QEBUFͰSFOEFSपΓͷνϡʔχϯάΛ͢Δ͜ͱ

    ΋ग़དྷΔ w 1VSF3FOEFS.JYJOΛ࢖͑Δ৔߹͸࢖͏͜ͱͰύϑΥʔϚϯε͕͕͋ Δ͜ͱ΋ w 1FSGPSNBODF5PPMT΋BEEPOͰެࣜʹఏڙ͞Ε͍ͯΔ
  66. 1FSGPSNBODF w DIJMEʹLFZΛࢦఆ͢Δ͜ͱͰ%0.Λ࠶ར༻ͯ͘͠ΕΔ͜ͱ΋͋Δ w 4UZMF͸จࣈྻͰͳͯ͘ɺΦϒδΣΫτͰࢦఆ͢Δ renderA: <div><span>first</span></div> renderB: <div><span>second</span><span>first</span></div> =>

    [replaceAttribute textContent 'second'], [insertNode <span>first</span>] renderA: <div><span key="first">first</span></div> renderB: <div><span key="second">second</span><span key="first">first</span></ div> => [insertNode <span>second</span>] renderA: <div style={{color: 'red'}} /> renderB: <div style={{fontWeight: 'bold'}} /> => [removeStyle color], [addStyle font-weight 'bold']
  67. "SDIJUFDUVSF

  68. 'MVY

  69. 'MVY w 'BDFCPPL͕ఏএ͍ͯ͠ΔΞʔΩςΫνϟ w ΞΫγϣϯͱ͍͏୯ҐͰΠϕϯτΛ %JTQBUDIFSܦ༝Ͱ΍ΓͱΓͯ͠ɺํ޲ͳॲ ཧͷྲྀΕΛ࡞Δ w 3FBDUΛ࢖ͬͯେن໛ΞϓϦΛ࡞Ζ͏ͱ͢Δ ͱ͜͏͍͏ઃܭʹͳΔͷ΋Θ͔Δؾ͕͢Δ

    w ৄࡉ͸ผ్Ͱʜ w IUUQGBDFCPPLHJUIVCJPqVY https://speakerdeck.com/fisherwebdev/fluxchat#11
  70. #BDLCPOF

  71. #BDLCPOF w 3FBDU͸.7$Ͱ͍͏7ͳͷͰɺ͋Δఔ౓ͷن໛ʹͳΔͱ.PEFMͱ͠ ͯ#BDLCPOFΛ࢖͏ํ๏΋ w CBDLCPOFSFBDUDPNQPOFOUͳΜ͍ͯ͏ͷ΋͋Δ w IUUQTHJUIVCDPNNBHBMIBTCBDLCPOFSFBDUDPNQPOFOU componentWillMount() {

    tracks.on("all", this.setTracks); }, componentWillUnmount() { tracks.off("all", this.setTracks); },
  72. 3PVUFS

  73. 3PVUFS w #BDLCPOF3PVUFSͰ΋EJSFDUPSͰ΋޷͖ͳ΋ͷΛ࢖͏ w SFBDUSPVUFS΍SFBDUSPVUFSDPNQPOFOUͱ͍͏$PNQPOFOUͰ ϧʔςΟϯάΛఆٛ͢ΔΑ͏ͳ΋ͷ΋͋Δ var Router = require(‘react-router-component’);

    var Locations = Router.Locations, Location = Router.Location ; var route = { <Locations path={this.props.path}> <Location path="/" handler={Top} /> <Location path="/artist" handler={Artist} /> <Location path="/country" handler={Country} /> </Locations> };
  74. 4FSWFS4JEF3FOEFSJOH

  75. 4FSWFS4JEF3FOEFSJOH w αʔόʔଆͰ$PNQPOFOUΛ)5.-Խͯ͠ฦ͢͜ͱ͕ग़དྷΔ w 1IBOUPN+4࢖ͬͯͷྗٕͳ͜ͱ͸͠ͳ͍͍ͯ͘ w SFBDUSBJMT΍FYQSFTTSFBDUWJFXTͱ͍͏ͷ΋ެࣜͰαϙʔτͯ͠ ͍ΔͷͰಋೖͷίετ͸ߴ͘ͳ͍ w IUUQTHJUIVCDPNSFBDUKT

    w SFBDUSPVUFSDPNQPOFOU͸4FSWFS4JEFͰͷ3FOEFSJOH΋αϙʔτ ͍ͯ͠ΔͷͰ૊Έ߹ΘͤΔͱ͍͍ w IUUQTHJUIVCDPNBOESFZQPQQSFBDUSPVUFSDPNQPOFOU
  76. 4FSWFS4JEF3FOEFSJOH w SFOEFS5P4USJOHͱSFOEFS5P4UBUJD.BSLVQͷͲͪΒ͔Λ࢖ͬͯ )5.-Λੜ੒͢Δ w SFOEFS5P4USJOH w ϑϩϯτଆͰ΋3FBDUKTΛ࢖ͬͯಈతʹ͍ͨ͠৔߹ʹ࢖͏ w SFOEFS5P4UBUJD.BSLVQ

    w ੩తͳ)5.- ϑϩϯτଆͰ3FBDUKTΛ࢖Θͳ͍ Λੜ੒͍ͨ͠ͱ ͖ʹ࢖͏
  77. SFOEFS5P4USJOH w αʔόʔଆͰ)5.-Λੜ੒ͯ͠ɺΫϥΠΞϯτଆͰ͸SFOEFSͰΠϕ ϯτ͕BUUBDI͞ΕΔ͚ͩ w αʔόʔଆͰੜ੒͞ΕΔ%0.ߏ଄ͱΫϥΠΞϯτଆͰੜ੒͞ΕΔ %0.ߏ଄͕ಉ͡ʹ͢Δඞཁ͕͋Δ w 1SPQͳͲ$PNQPOFOUͷੜ੒ʹඞཁͳσʔλΛαʔόʔɺΫϥ ΠΞϯτͰڞ༗͢Δඞཁ͕͋Δ

    w DPNQPOFOU8JMM.PVOU͸4FSWFS4JEF3FOEFSJOH࣌ʹ΋ݺ͹ΕΔͷ ͰαʔόʔͰ΋ಈ࡞͢ΔίʔυͰ͋Δඞཁ͕͋Δ
  78. SFOEFS$PNQPOFOU5P4USJOH w ੜ੒͞ΕΔ)5.-ʹ͸EBUBSFBDUJEͱEBUBSFBDUDIFDLTVN͕ઃ ఆ͞ΕΔ

  79. SFOEFS5P4UBUJD.BSLVQ w ੩తͳ)5.- EBUBSFBDUJEͳͲ͕ͳ͍ ͕ੜ੒͞ΕΔ w ϑϩϯτଆͰ3FBDUKTΛ࢖ͬͯSFOEFS$PNQPOFOU͢Δͱɺαʔόʔ ଆͰੜ੒ͨ͠)5.-Λ࢖Θͣʹ৽͘͠%0.Λ࡞ͬͯඳը͞ΕΔ w FYQSFTTSFBDUWJFXT͸͜ͷϝιουΛ࢖͍ͬͯΔ

    w ੜ੒͞ΕΔ)5.-
  80. w XJUIFYQSFTT 4BNQMF$PEF4FSWFS4JEF require('node-jsx').install({ harmony: true }); // JSXΛparseग़དྷΔΑ͏ʹ var

    React = require(‘react’); var App = require('./src/index.jsx'); // ComponentΛಡΈࠐΉ var handler = function(name) { return function(req, res) { // Prop౉ͯ͠ComponentΛHTMLԽ var html = React.renderToString( React.createElement(App, { path: "/" + name }) ); res.send(html); }; }; app.get('/', handler('')); app.get('/artist', handler('artist')); app.get('/country', handler('country'));
  81. 4BNQMF$PEF$PNQPOFOU  var App = React.createClass({ render() { var title

    = `Artist Top Tracks (${ this.props.path })`; return ( <html lang="ja"> <head> // <title>xxx - {title}</title>ͱ΍Δͱ<span>{title}</span>ʹͳΔͷͰ஫ҙ <title>{title}</title> : </head> <body> <div id="app" className="container"> <Locations path={this.props.path}> <Location path="/" handler={Top} /> </Locations> </div> </body> <script src="bundle.js"></script> </html> ); } });
  82. 4BNQMF$PEF$PNQPOFOU  w αʔόʔଆͱΫϥΠΞϯτଆͰॲཧΛ෼͚Δ w ࣮ࡍ͸+40/Λ)5.-ʹຒΊࠐΉͳͲͰ1SPQΛڞ༗͢Δ w IUUQCFOBMQFSUDPNQSFWFOUJOHYTTKTPOIUNM var App

    = React.createClass({ render() {…} }); if (typeof window !== "undefined") { React.render(<App path={window.location.pathname} />, document); } else { module.exports = App; }
  83. 4BNQMF$PEF w IUUQTHJUIVCDPNLPCBSFBDUCPJMFSQMBUF w IUUQSFBDUTFSWFSTJEFSFOEFSJOHIFSPLVBQQDPN %&.0

  84. $PODMVTJPO

  85. $PODMVTJPO w ͪΐͬͱͨ͠ΞϓϦॻ͘ͳΒ7VFKTͱ͔XBZσʔλόΠϯσΟϯ άͷϥΠϒϥϦ࢖ͬͯॻ͘ͷָ͕͚ͩͲɺ͕ͬͭΓ࡞Δ࣌ͷ7JFXͱ ͯ͠3FBDUKT࢖͏ͷ͸ΞϦͳؾ͕͢Δ w 4&0͕ඞཁͳ৔໘Ͱ4FSWFS4JEF3FOEFSJOH͍͔ͨ͠Β࢖͏ͱ͍͏ બ୒΋͋Γͦ͏ w +49ͱֶ͔͋ͬͯशίετߴͦ͏ͳҹ৅͚͋ͬͨͲ"1*΋γϯϓϧ

    ͰެࣜಡΊ͹े෼ཧղग़དྷΔ w IUUQGBDFCPPLHJUIVCJPSFBDUEPDT
  86. TQFBLFSEFDLDPNLPCB HJUIVCDPNLPCBSFBDUCPJMFSQMBUF