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

Get Started React.js

koba04
September 30, 2014

Get Started React.js

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

koba04

September 30, 2014
Tweet

More Decks by koba04

Other Decks in Programming

Transcript

  1. 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
  2. )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') );
  3. )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ͭ
  4. 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’);
  5. 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') );
  6. &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/
  7. &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>); } });
  8. $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') );
  9. 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);
  10. 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> ); } });
  11. 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> ); } });
  12. 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} />
  13. $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͔Β࡟আ͞ΕΔͱ͖ʹݺ͹ΕΔ ΫϦʔϯΞοϓॲཧʹద͍ͯ͠Δ
  14. $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 } ) }); }, };
  15. '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 } ) }); } },
  16. 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> } });
  17. 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’]} />
  18. 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” /> ) }
  19. 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) => { … });
  20. 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} />
  21. &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); } },
  22. '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}); }
  23. '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>
  24. .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 } ) }); }, };
  25. .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: [] ࢦఆ͢ΔͱΤϥʔʹͳΔ }; } });
  26. $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> ): }
  27. "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> ); } });
  28. "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 …
  29. "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}>
  30. 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"});
  31. 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']
  32. 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> };
  33. 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'));
  34. 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> ); } });
  35. 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; }