Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dive into React.js

45daf58c77e9dbbab5a1c8a5afc7ac5c?s=47 koba04
February 15, 2015

Dive into React.js

歌舞伎座.tech#6「VirtualDOMとReact」での発表資料です。
http://kbkz.connpass.com/event/11254/

45daf58c77e9dbbab5a1c8a5afc7ac5c?s=128

koba04

February 15, 2015
Tweet

Transcript

  1. %JWFJOUP3FBDUKT Վ෣ب࠲UFDI   !LPCB

  2. !LPCB w 8FC"QQMJDBUJPO&OHJOFFS w IUUQLPCBDPN

  3. None
  4. 3FBDUKT

  5. http://facebook.github.io/react/

  6. 3FBDUKTFWFSZXIFSF

  7. https://www.facebook.com/

  8. http://instagram.com/

  9. http://instagram.com/

  10. https://speakerdeck.com/mridgway/isomorphic-flux

  11. None
  12. http://facebook.github.io/react/docs/videos.html

  13. https://developer.atlassian.com/blog/2015/02/rebuilding-hipchat-with-react/

  14. https://vivaldi.com/

  15. http://techblog.netflix.com/2015/01/netflix-likes-react.html

  16. 3FBDUXPSLTXJUIPVUUIF%0. https://github.com/reactjs/react-art/blob/master/src/ReactART.js

  17. http://engineering.flipboard.com/2015/02/mobile-web/

  18. None
  19. $PNQPOFOU

  20. $PNQPOFOU w $PNQPOFOUΛ࡞ͬͯ࢖͏ɻશͯ͸$PNQPOFOU୯ҐͰߟ͑Δɻ w EJW΋3FBDUKT͕ఆ͍ٛͯ͠Δ$PNQPOFOUɻ var Hello = React.createClass({ render()

    { return <div>Hello {this.props.name}</div>; } }); React.render(<Hello name=“kbkz” />, document.body); // <div>Hello kbkz</div>
  21. &4$MBTT WCFUB class Hello extends React.Component { render() { return

    <div>Hello {this.props.name}</div>; } } React.render(<Hello name=“kbkz” />, document.body); // <div>Hello kbkz</div>
  22. $PNQPOFOU &MFNFOU ReactComponentClass = React.createClass({ render(){} }); ReactCompositeComponent = React.render(<ReactComponentClass

    />, document); ReactCompositeComponent.setState({}); ReactDOMComponent = React.render(<div />, document); ReactDOMComponent.setState({}); // Uncaught TypeError: undefined is not a function ReactElement = React.createElement(ReactComponentClass, null); // ReactElement has type, props(children), key, ref.
  23. 1SPQBOE4UBUF

  24. 1SPQJTJNNVUBCMF w 1SPQ͸$PNQPOFOU͕֎෦͔Βड͚෇͚Δ஋Ͱมߋ͸ෆՄɻ w 1SPQͷσʔλ͸ॴ༗͍ͯ͠ͳ͍σʔλɻ w )BOEMFSΛड͚෇͚ΔΑ͏ʹͯ͠਌ʹॲཧΛҕৡͨ͠Γɻ var Foods =

    React.createClass({ render() { var foods = [‘sushi’, ‘ramen’, ‘pizza’].map( food => { return <Food name={food} /> }); return <div>{foods}</div>; } var Food = React.createClass({ render() { return <ul><li>{this.props.name}</li></ul> } }); ͜ͷσʔλ͸ Foodsͷ΋ͷ
  25. 1SPQJTB4QFD w ֎෦ͱͷ*'࢓༷ͱͳΔɻ w 1SPQ5ZQFTͰ໌֬ʹఆٛ͢Δɻ var Food = React.createClass({ propTypes:

    { name: React.PropTypes.string.isRequired }, render() { return <ul><li>{this.props.name}</li></ul> } }); จࣈྻͰඞਢ
  26. 1SPQ5ZQFT  React.PropTypes.array // ഑ྻ React.PropTypes.bool.isRequired // BooleanͰඞਢ React.PropTypes.func //

    ؔ਺ React.PropTypes.number // ਺஋ React.PropTypes.object // ΦϒδΣΫτ React.PropTypes.string // จࣈྻ React.PropTypes.node // RenderͰ͖Δ΋ͷ React.PropTypes.element.isRequired // React ElementͰඞਢ React.PropTypes.instanceOf(XXX) // XXXͷinstance͔Ͳ͏͔
  27. 1SPQ5ZQFT  React.PropTypes.oneOf(['foo', 'bar']) // foo͔bar React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.array]) // จࣈྻ͔഑ྻ

    React.PropTypes.arrayOf(React.PropTypes.string) // จࣈྻͷ഑ྻ͔Ͳ͏͔ React.PropTypes.objectOf(React.PropTypes.string) // จࣈྻͷ஋Λ͍࣋ͬͯΔ͔ React.PropTypes.shape({ // ࢦఆ͞ΕͨܗࣜΛຬ͍ͨͯ͠Δ͔ color: React.PropTypes.string, fontSize: React.PropTypes.number }); React.PropTypes.any.isRequired // ͳΜͰ΋͍͍͚Ͳඞਢ // ΧελϜͷ੍໿΋ఆٛग़དྷΔ(μϝͳ৔߹͸Error౤͛Δ) customPropType: function(props, propName, componentName) { if (!/^[0-9]/.test(props[propName])) { return new Error('Validation failed!'); } }
  28. 4UBUFJTNVUBCMF w 4UBUF͸ͦͷ$PNQPOFOU͕ঢ়ଶΛ؅ཧ͢Δ஋ɻ w TUBUFࣗମ͸JNNVUBCMFͱͯ͠ѻ͍ඞͣTFU4UBUFͰߋ৽͢Δɻ var Counter = React.createClass({ getInitialState()

    { return { count: 0 }; }, onClick() { this.setState({ count: this.state.count + 1}); }, render() { return ( <div> <span>{this.state.count}</span> <button onClick={this.onClick}>click</button> </div> ); } });
  29. 1SFEJDUBCMF

  30. 0XOFSTIJQ w $PNQPOFOUؒͷ਌ࢠؔ܎Λҙࣝ͢Δɻ Image Image Food Food Foods setState({ foods:

    [ { name: “sushi”, img: “sushi.png” }, { name: “ramen”, img: “ramen.png”} ] }); <Food food={foods[1]} /> <Food food={foods[0]} /> <Image src=“ramen.png” /> <Image src=“sushi.png” /
  31. 3FVTFBCMF w $PNQPOFOUͷ໾ׂΛҙࣝ͢Δɻ Image Image Food Food Foods click! onClick()

    { this.props.onImageClick(); } onImageClick() { this.props.onFoodClick(this.props.food); }; onFoodClick(food) { : this.setState({ foods: newFoods }); };
  32. 3FSFOEFSUIFFOUJSFBQQ w 3FBDUKT͕%0.ͷࠩ෼ߋ৽Λ΍ͬͯ͘ΕΔͷͰɺΞϓϦͷσʔ λΛ·ͱΊ͓͍ͯ࣋ͬͯͯTFU4UBUFͰߋ৽͢ΔΑ͏ͳࡶͳΞʔ ΩςΫνϟ΋ՄೳʹͳΔɻ

  33. 7JSUVBM%0.JTJOGSBTUSVDUVSF w 7JSUVBM%0.ͷ࣮૷͍ͭͯ࢖͏ଆ͸ؾʹ͢Δඞཁ͸ͳ͍ɻ w Ϧετʹର͢ΔLFZͷࢦఆ͘Β͍ɻ w ܭࢉྔΛݮΒͨ͢Ίʹ৭ʑͱ޻෉ͯͨ͠Γ͸͢Δɻ w ಉ֊૚ɺ$PNQPOFOUಉ࢜Ͱͷൺֱʜ w

    IUUQDBMFOEBSQFSGQMBOFUDPNEJ⒎ render() { var li = this.state.items.map((item) => <li key={item.id}>{item.name}</li> ); return <ul>{li}</ul>; }
  34. -JGFDZDMF

  35. $PNQPOFOUMJGFDZDMF w .PVOUJOH w DPNQPOFOU8JMM.PVOU DPNQPOFOU%JE.PVOU  w 6QEBUJOH w

    DPNQPOFOU8JMM3FDFJWF1SPQT TIPVME$PNQPOFOU6QEBUF  DPNQPOFOU8JMM6QEBUF DPNQPOFOU%JE6QEBUF w 6ONPVOUJOH w DPNQPOFOU8JMM6ONPVOU
  36. +49

  37. +49 w +VTUTZOUBYTVHBSPG3FBDUDSFBUF&MFNFOU w +49JTPQUJPOBM w 4FQBSBUJPOPGDPODFSOT 㱠UFDIOPMPHJFT  w

    4QSFBE"UUSJCVUFT <div className=“container”>{this.props.name}</div> ! React.createElement("div", {className: "container"}, this.props.name); <User {...this.props} type=“user”} />
  38. SFBDUUPPMT w KTYίϚϯυ΍ม׵ϥΠϒϥϦ͕ೖ͍ͬͯΔɻ w CSPXTFSJGZͷUSBOTGPSNͰ͋ΔSFBDUJGZ΍ɺXFCQBDLͷMPBEFS Ͱ͋ΔKTYMPBEFSͰ΋࢖ΘΕ͍ͯΔɻ w IBSNPOZPQUJPOΛ༗ޮʹ͢Δ͜ͱͰҰ෦ͷ&4 GFBUVSFΛ ࢖͏͜ͱ͕ग़དྷΔɻ

  39. #BCFM UP w #BCFMࣗମ͕+49Λαϙʔτ͍ͯ͠ΔͷͰ#BCFM͚ͩͰSFBDU UPPMT͸࢖͏ඞཁ͕ͳ͍ɻ w IUUQCMPHLPCBDPNQPTUB DPNCJOBUJPOPGSFBDUKTBOEUP w CSPXTFSJGZ

    CBCFMJGZɺXFCQBDL CBCFMMPBEFS͚ͩͰ 0,ɻ w +49ͷQBSTFSʹ͸33FWFSTFSBDPSOKTYΛ࢖͍ͬͯΔɻ
  40. #SPXTFSFOWJSPONFOU

  41. SFGHFU%0./PEF w SFG͸$PNQPOFOUʹର͢ΔࢀরΛऔಘ͢ΔͨΊͷ1SPQɻ w HFU%0./PEFͱ૊Έ߹Θͤͯ࢖͏΋ͷ͘Β͍ʹʜɻ w HFU%0./PEF͸%0.΁ͷࢀরΛऔಘ͢ΔͨΊͷ"1*ɻ w W͔Β͸3FBDUpOE%0./PEFʹɻ onComponentDidMount()

    { this.refs.input.getDOMNode().focus(); }, render() { return <input type=“text” ref=“input” />; }
  42. EBOHFSPVTMZ4FU*OOFS)5.- w )5.-Λͦͷ··౉͍ͨ͠ͱ͖ʹ࢖͏ɻ w ஫ҙͯ͠࢖͏͜ͱΛڧௐͨ͠*'ɻ w ໊લ͸มΘΔ͔΋ɻ *TTVFͰٞ࿦த  w

    IUUQTHJUIVCDPNGBDFCPPLSFBDUJTTVFT createMarkup(data) { return { __html: someEncodeAPI(data) }; }, render() { return <div dangerouslySetInnerHTML={this.createMarkup(data)} /> }
  43. 4ZOUIFUJD&WFOU w ࣮ࡍʹ&WFOU-JTUFOFSΛొ࿥͍ͯ͠Δͷ͸SPPUͷཁૉʹ͚ͩͰɺ σϦήʔτʹΑ֤ͬͯ$PNQPOFOUͷΠϕϯτͱͯ͠Ϛοϐϯά ͍ͯ͠Δɻ onChange(e) { this.setState({ text: e.target.value

    }); } onClick(e) { e.preventDefault(); } render() { return ( <div> <input type=“text” onChange={this.onChange} />; <a onClick={this.onClick}>no longer</a> </div> ); } Autobinding Not support in Class syntax
  44. )PXUPUFTU

  45. )PXUPUFTU w 3FBDUBEEPOT5FTU6UJMTSFOEFS*OUP%PDVNFOUͰ%0.ʹ௥Ճ ͯ͠ॻ͍͍ͯ͘ײ͡ɻ describe("handleSubmit", () => { let inputArtist,

    preventDefault; beforeEach(() => { inputArtist = React.addons.TestUtils.renderIntoDocument(<InputArtist />); preventDefault = jest.genMockFunction(); inputArtist.setState({ inputArtist: 'travis' }); React.addons.TestUtils.Simulate.submit(inputArtist.getDOMNode(), { preventDefault: preventDefault }); }); it ("calls fetchByArtist with state.inputArtist", () => { expect(AppTracksActionCreators.fetchByArtist).toBeCalled(); expect(AppTracksActionCreators.fetchByArtist).toBeCalledWith('travis'); }); it ("calls e.preventDefault", () => { expect(preventDefault).toBeCalled(); });
  46. 3PVUJOH

  47. SBDLUSFBDUSPVUFS w $PNQPOFOUͰϧʔςΟϯάΛఆ͍ٛͯ͘͠ɻ w TFSWFSTJEFSFOEFSJOH΍ωετͨ͠ϧʔςΟϯάͳͲʹ΋ର Ԡ͍ͯͯ͠ଟػೳɻ var routes = (

    <Route name="top" handler={App} path="/"> <Route name="artist" handler={Artist} /> <Route name="country" handler={Country} /> <DefaultRoute handler={Top} /> </Route> ); Router.run(routes, Router.HistoryLocation, (Handler) => { React.render(<Handler />, document); });
  48. 4FSWFSTJEFSFOEFSJOH

  49. 4FSWFSTJEFSFOEFSJOH w 3FBDUKTͰ͸ɺ$PNQPOFOUͷঢ়ଶΛΦϒδΣΫτͱͯ࣋ͬ͠ ͍ͯͯ؀ڥʹΑΒͣ)5.-ͱͯ͠ు͖ग़͢͜ͱ͕ग़དྷΔɻ w 4&0ɺJOJUJBMMPBEͷͨΊʹɻ w TFSWFSଆͰͷ+49ͷύʔε͸OPEFKTYΛ࢖ͬͨΓUP UP OPEFDPNNBOE

    Λ࢖ͬͨΓɻ w 4UPSFͷσʔλΛ4FSWFSͱ#SPXTFSͰڞ༗͢Δɻ
  50. )PXUPJNQMFNFOU w SFOEFS5P4USJOH w ϑϩϯτଆͰ΋3FBDUKTΛ࢖͍͍ͨ৔߹ʹ࢖͏ɻEBUB SFBDUJEͳͲ͕෇Ճ͞Εͨ)5.-͕ੜ੒͞ΕΔɻ w ϝΠϯ͸ͬͪ͜ɻ w SFOEFS5P4UBUJD.BSLVQ

    w EBUBSFBDUJEͳͲ͕෇͍ͯͳ͍୯७ͳ)5.-Λฦ͢ͷͰ੩త ͳϖʔδΛੜ੒͍ͨ͠৔߹ʹ࢖͏ɻ
  51. 3FBDUSFOEFS5P4USJOH server HTTP Request data-reactid, data-react-checksum&ॳظσʔλ ෇͖ͷHTML React.renderͰੜ੒ͨ͠HTMLͱserver͔Βͷdata-react-checksumΛൺֱ React.render Ұக͢Ε͹EventListenerΛઃఆ͢Δ͚ͩͰɺ͠ͳ͍৔߹͸React.renderͰ

    ੜ੒ͨ͠HTMLΛinnerHTML্ͯ͠ॻ͖ͯ͠DOMΛ࠶ߏங͢Δ
  52. 'MVY

  53. 'MVYJTBBSDIJUFDUVSF https://github.com/facebook/flux

  54. 'MVYJTBBSDIJUFDUVSF w GBDFCPPLqVYʹ͸EJTQBUDIFS͔͠ͳ͍ɻ w ͦͷଞ͸4UPSF͕&WFOU&NJUUFSܧঝͯ͠Δ͘Β͍ɻ w ΑΓগͳ͍ίʔυͰॻ͖΍ͨ͘͢͠ΓɺTFSWFSTJEFSFOEFSJOH ʹରԠ͢ΔͨΊʹɺ৭ʑͳ'MVY࣮૷͕ཚཱ͍ͯ͠Δɻ w IUUQTHJUIVCDPNWPSPOJBOTLJqVYDPNQBSJTPO

    w TFOTJUJWFͳ෦෼͸3FBDUKT͕໘౗Λݟͯ͘ΕΔͷͰɺ'MVYͰΞ ϓϦέʔγϣϯͷॲཧͷྲྀΕΛ୯७ʹɻ
  55. %JTQBUDIFS import {Dispatcher} = from ‘flux’; import assign = from

    ‘object-assign’; import AppConstants = from ‘../constants/AppConstants’; ; let PayloadSources = AppConstants.PayloadSources; export default assign(new Dispatcher(), { handleViewAction: function(action) { this.dispatch({ source: PayloadSources.VIEW_ACTION, action: action }); } });
  56. 4UPSF let tracks = []; let TrackStore = assign({}, EventEmitter.prototype,

    { emitChange: function() { this.emit(CHANGE_EVENT); }, addChangeListener: function(callback) { this.on(CHANGE_EVENT, callback); }, removeChangeListener: function(callback) { this.removeListener(CHANGE_EVENT, callback); }, getAll: function() { return tracks; }, }); TrackStore.dispatchToken = AppDispatcher.register(function(payload) { let action = payload.action; switch (action.type) { case ActionTypes.RECEIVE_TRACKS_BY_ARTIST: tracks = action.tracks; TrackStore.emitChange(); break;
  57. "DUJPO$SFBUPST export default { fetchByArtist: function(artist) { request.get( `url${encodeURIComponent(artist)}`, (res)

    => { AppDispatcher.handleViewAction({ type: ActionTypes.RECEIVE_TRACKS_BY_ARTIST, tracks: res.body.toptracks.track }); } ); }, }
  58. 7JFX export default React.createClass({ getInitialState() { return { tracks: TrackStore.getAll(),

    }; }, componentDidMount: function() { TrackStore.addChangeListener(this._onChange); }, componentWillUnmount: function() { TrackStore.removeChangeListener(this._onChange); }, _onChange: function() { this.setState({ tracks: TrackStore.getAll() }); }, handleSubmit(e) { e.preventDefault(); let artist = this.state.inputArtist; if (artist) AppTracksActionCreators.fetchByArtist(artist); }, });
  59. $PODMVTJPO

  60. 3FBDUKTJTGBTU

  61. "EWBOUBHFPG7JSUVBM%0. w ଎͍ʁ w K2VFSZͳͲͰ%0.৮ͬͯॻ͍ͨํ͕଎͍ɻ w %0.Λҙࣝͤͣʹࡶʹ޿ൣғͰ$PNQPOFOUΛߋ৽ͯ͠΋ͦ ͦ͜͜଎͍ɻ w TIPVME$PNQPOFOU6QEBUFʹΑΔ࠷దԽɻ

  62. 3FBDUKTJTFBTZ

  63. .BLFEFWFMPQNFOUFBTJFS w %0.΁ͷ൓өΛ3FBDUKTʹ೚ͤΔ͜ͱͰΞϓϦέʔγϣϯͷ ίʔυΛॻ͘͜ͱʹ஫ྗग़དྷΔɻ w +BWB4DSJQUͷ஌ࣝΛͦͷ··׆͔ͨ͠։ൃɻ

  64. $PODMVTJPO w 3FBDUKT͸։ൃ଎౓ΛߴΊΔ͜ͱ͕໨తͰ͸ͳͯ͘ɺΞϓϦ έʔγϣϯͷ৴པੑΛߴΊΔ͜ͱ͕໨తɻ w 7JFXͷߋ৽Λ3FBDUKTʹ೚͓ͤͯ͘͜ͱͰΞϓϦέʔγϣϯͷ ίʔυΛγϯϓϧʹॻ͘͜ͱ͕ग़དྷΔɻ w $PNQPOFOUΛ࡞Δ͚ͩͳͷͰϥΠϒϥϦͷαΠζ͸͋Δ΋ͷ ͷಋೖ͠΍͍͢ɻ

    w 3FBDUKT͸ࠓ࢖ΘΕ͍ͯΔٕज़ɻ
  65. 5IBOLZPV