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. 3.
  2. 11.
  3. 18.
  4. 19.
  5. 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>
  6. 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>
  7. 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.
  8. 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ͷ΋ͷ
  9. 25.

    1SPQJTB4QFD w ֎෦ͱͷ*'࢓༷ͱͳΔɻ w 1SPQ5ZQFTͰ໌֬ʹఆٛ͢Δɻ var Food = React.createClass({ propTypes:

    { name: React.PropTypes.string.isRequired }, render() { return <ul><li>{this.props.name}</li></ul> } }); จࣈྻͰඞਢ
  10. 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͔Ͳ͏͔
  11. 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!'); } }
  12. 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> ); } });
  13. 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” /
  14. 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 }); };
  15. 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>; }
  16. 34.
  17. 35.

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

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

    +49

  19. 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”} />
  20. 42.

    EBOHFSPVTMZ4FU*OOFS)5.- w )5.-Λͦͷ··౉͍ͨ͠ͱ͖ʹ࢖͏ɻ w ஫ҙͯ͠࢖͏͜ͱΛڧௐͨ͠*'ɻ w ໊લ͸มΘΔ͔΋ɻ *TTVFͰٞ࿦த  w

    IUUQTHJUIVCDPNGBDFCPPLSFBDUJTTVFT createMarkup(data) { return { __html: someEncodeAPI(data) }; }, render() { return <div dangerouslySetInnerHTML={this.createMarkup(data)} /> }
  21. 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
  22. 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(); });
  23. 46.
  24. 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); });
  25. 52.
  26. 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 }); } });
  27. 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;
  28. 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 }); } ); }, }
  29. 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); }, });