Slide 1

Slide 1 text

%JWFJOUP3FBDUKT Վ෣ب࠲UFDI !LPCB

Slide 2

Slide 2 text

!LPCB w 8FC"QQMJDBUJPO&OHJOFFS w IUUQLPCBDPN

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

3FBDUKT

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

3FBDUKTFWFSZXIFSF

Slide 7

Slide 7 text

https://www.facebook.com/

Slide 8

Slide 8 text

http://instagram.com/

Slide 9

Slide 9 text

http://instagram.com/

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

http://facebook.github.io/react/docs/videos.html

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

https://vivaldi.com/

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

$PNQPOFOU

Slide 20

Slide 20 text

$PNQPOFOU w $PNQPOFOUΛ࡞ͬͯ࢖͏ɻશͯ͸$PNQPOFOU୯ҐͰߟ͑Δɻ w EJW΋3FBDUKT͕ఆ͍ٛͯ͠Δ$PNQPOFOUɻ var Hello = React.createClass({ render() { return
Hello {this.props.name}
; } }); React.render(, document.body); //
Hello kbkz

Slide 21

Slide 21 text

&4$MBTT WCFUB class Hello extends React.Component { render() { return
Hello {this.props.name}
; } } React.render(, document.body); //
Hello kbkz

Slide 22

Slide 22 text

$PNQPOFOU &MFNFOU ReactComponentClass = React.createClass({ render(){} }); ReactCompositeComponent = React.render(, document); ReactCompositeComponent.setState({}); ReactDOMComponent = React.render(
, document); ReactDOMComponent.setState({}); // Uncaught TypeError: undefined is not a function ReactElement = React.createElement(ReactComponentClass, null); // ReactElement has type, props(children), key, ref.

Slide 23

Slide 23 text

1SPQBOE4UBUF

Slide 24

Slide 24 text

1SPQJTJNNVUBCMF w 1SPQ͸$PNQPOFOU͕֎෦͔Βड͚෇͚Δ஋Ͱมߋ͸ෆՄɻ w 1SPQͷσʔλ͸ॴ༗͍ͯ͠ͳ͍σʔλɻ w )BOEMFSΛड͚෇͚ΔΑ͏ʹͯ͠਌ʹॲཧΛҕৡͨ͠Γɻ var Foods = React.createClass({ render() { var foods = [‘sushi’, ‘ramen’, ‘pizza’].map( food => { return }); return
{foods}
; } var Food = React.createClass({ render() { return
  • {this.props.name}
} }); ͜ͷσʔλ͸ Foodsͷ΋ͷ

Slide 25

Slide 25 text

1SPQJTB4QFD w ֎෦ͱͷ*'࢓༷ͱͳΔɻ w 1SPQ5ZQFTͰ໌֬ʹఆٛ͢Δɻ var Food = React.createClass({ propTypes: { name: React.PropTypes.string.isRequired }, render() { return
  • {this.props.name}
} }); จࣈྻͰඞਢ

Slide 26

Slide 26 text

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͔Ͳ͏͔

Slide 27

Slide 27 text

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!'); } }

Slide 28

Slide 28 text

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 (
{this.state.count} click
); } });

Slide 29

Slide 29 text

1SFEJDUBCMF

Slide 30

Slide 30 text

0XOFSTIJQ w $PNQPOFOUؒͷ਌ࢠؔ܎Λҙࣝ͢Δɻ Image Image Food Food Foods setState({ foods: [ { name: “sushi”, img: “sushi.png” }, { name: “ramen”, img: “ramen.png”} ] });

Slide 31

Slide 31 text

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 }); };

Slide 32

Slide 32 text

3FSFOEFSUIFFOUJSFBQQ w 3FBDUKT͕%0.ͷࠩ෼ߋ৽Λ΍ͬͯ͘ΕΔͷͰɺΞϓϦͷσʔ λΛ·ͱΊ͓͍ͯ࣋ͬͯͯTFU4UBUFͰߋ৽͢ΔΑ͏ͳࡶͳΞʔ ΩςΫνϟ΋ՄೳʹͳΔɻ

Slide 33

Slide 33 text

7JSUVBM%0.JTJOGSBTUSVDUVSF w 7JSUVBM%0.ͷ࣮૷͍ͭͯ࢖͏ଆ͸ؾʹ͢Δඞཁ͸ͳ͍ɻ w Ϧετʹର͢ΔLFZͷࢦఆ͘Β͍ɻ w ܭࢉྔΛݮΒͨ͢Ίʹ৭ʑͱ޻෉ͯͨ͠Γ͸͢Δɻ w ಉ֊૚ɺ$PNQPOFOUಉ࢜Ͱͷൺֱʜ w IUUQDBMFOEBSQFSGQMBOFUDPNEJ⒎ render() { var li = this.state.items.map((item) =>
  • {item.name}
  • ); return
      {li}
    ; }

    Slide 34

    Slide 34 text

    -JGFDZDMF

    Slide 35

    Slide 35 text

    $PNQPOFOUMJGFDZDMF w .PVOUJOH w DPNQPOFOU8JMM.PVOU DPNQPOFOU%JE.PVOU w 6QEBUJOH w DPNQPOFOU8JMM3FDFJWF1SPQT TIPVME$PNQPOFOU6QEBUF DPNQPOFOU8JMM6QEBUF DPNQPOFOU%JE6QEBUF w 6ONPVOUJOH w DPNQPOFOU8JMM6ONPVOU

    Slide 36

    Slide 36 text

    +49

    Slide 37

    Slide 37 text

    +49 w +VTUTZOUBYTVHBSPG3FBDUDSFBUF&MFNFOU w +49JTPQUJPOBM w 4FQBSBUJPOPGDPODFSOT 㱠UFDIOPMPHJFT w 4QSFBE"UUSJCVUFT
    {this.props.name}
    ! React.createElement("div", {className: "container"}, this.props.name);

    Slide 38

    Slide 38 text

    SFBDUUPPMT w KTYίϚϯυ΍ม׵ϥΠϒϥϦ͕ೖ͍ͬͯΔɻ w CSPXTFSJGZͷUSBOTGPSNͰ͋ΔSFBDUJGZ΍ɺXFCQBDLͷMPBEFS Ͱ͋ΔKTYMPBEFSͰ΋࢖ΘΕ͍ͯΔɻ w IBSNPOZPQUJPOΛ༗ޮʹ͢Δ͜ͱͰҰ෦ͷ&4 GFBUVSFΛ ࢖͏͜ͱ͕ग़དྷΔɻ

    Slide 39

    Slide 39 text

    #BCFM UP w #BCFMࣗମ͕+49Λαϙʔτ͍ͯ͠ΔͷͰ#BCFM͚ͩͰSFBDU UPPMT͸࢖͏ඞཁ͕ͳ͍ɻ w IUUQCMPHLPCBDPNQPTUB DPNCJOBUJPOPGSFBDUKTBOEUP w CSPXTFSJGZCBCFMJGZɺXFCQBDLCBCFMMPBEFS͚ͩͰ 0,ɻ w +49ͷQBSTFSʹ͸33FWFSTFSBDPSOKTYΛ࢖͍ͬͯΔɻ

    Slide 40

    Slide 40 text

    #SPXTFSFOWJSPONFOU

    Slide 41

    Slide 41 text

    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 ; }

    Slide 42

    Slide 42 text

    EBOHFSPVTMZ4FU*OOFS)5.- w )5.-Λͦͷ··౉͍ͨ͠ͱ͖ʹ࢖͏ɻ w ஫ҙͯ͠࢖͏͜ͱΛڧௐͨ͠*'ɻ w ໊લ͸มΘΔ͔΋ɻ *TTVFͰٞ࿦த w IUUQTHJUIVCDPNGBDFCPPLSFBDUJTTVFT createMarkup(data) { return { __html: someEncodeAPI(data) }; }, render() { return
    }

    Slide 43

    Slide 43 text

    4ZOUIFUJD&WFOU w ࣮ࡍʹ&WFOU-JTUFOFSΛొ࿥͍ͯ͠Δͷ͸SPPUͷཁૉʹ͚ͩͰɺ σϦήʔτʹΑ֤ͬͯ$PNQPOFOUͷΠϕϯτͱͯ͠Ϛοϐϯά ͍ͯ͠Δɻ onChange(e) { this.setState({ text: e.target.value }); } onClick(e) { e.preventDefault(); } render() { return ( ); } Autobinding Not support in Class syntax

    Slide 44

    Slide 44 text

    )PXUPUFTU

    Slide 45

    Slide 45 text

    )PXUPUFTU w 3FBDUBEEPOT5FTU6UJMTSFOEFS*OUP%PDVNFOUͰ%0.ʹ௥Ճ ͯ͠ॻ͍͍ͯ͘ײ͡ɻ describe("handleSubmit", () => { let inputArtist, preventDefault; beforeEach(() => { inputArtist = React.addons.TestUtils.renderIntoDocument(); 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(); });

    Slide 46

    Slide 46 text

    3PVUJOH

    Slide 47

    Slide 47 text

    SBDLUSFBDUSPVUFS w $PNQPOFOUͰϧʔςΟϯάΛఆ͍ٛͯ͘͠ɻ w TFSWFSTJEFSFOEFSJOH΍ωετͨ͠ϧʔςΟϯάͳͲʹ΋ର Ԡ͍ͯͯ͠ଟػೳɻ var routes = ( ); Router.run(routes, Router.HistoryLocation, (Handler) => { React.render(, document); });

    Slide 48

    Slide 48 text

    4FSWFSTJEFSFOEFSJOH

    Slide 49

    Slide 49 text

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

    Slide 50

    Slide 50 text

    )PXUPJNQMFNFOU w SFOEFS5P4USJOH w ϑϩϯτଆͰ΋3FBDUKTΛ࢖͍͍ͨ৔߹ʹ࢖͏ɻEBUB SFBDUJEͳͲ͕෇Ճ͞Εͨ)5.-͕ੜ੒͞ΕΔɻ w ϝΠϯ͸ͬͪ͜ɻ w SFOEFS5P4UBUJD.BSLVQ w EBUBSFBDUJEͳͲ͕෇͍ͯͳ͍୯७ͳ)5.-Λฦ͢ͷͰ੩త ͳϖʔδΛੜ੒͍ͨ͠৔߹ʹ࢖͏ɻ

    Slide 51

    Slide 51 text

    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Λ࠶ߏங͢Δ

    Slide 52

    Slide 52 text

    'MVY

    Slide 53

    Slide 53 text

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

    Slide 54

    Slide 54 text

    'MVYJTBBSDIJUFDUVSF w GBDFCPPLqVYʹ͸EJTQBUDIFS͔͠ͳ͍ɻ w ͦͷଞ͸4UPSF͕&WFOU&NJUUFSܧঝͯ͠Δ͘Β͍ɻ w ΑΓগͳ͍ίʔυͰॻ͖΍ͨ͘͢͠ΓɺTFSWFSTJEFSFOEFSJOH ʹରԠ͢ΔͨΊʹɺ৭ʑͳ'MVY࣮૷͕ཚཱ͍ͯ͠Δɻ w IUUQTHJUIVCDPNWPSPOJBOTLJqVYDPNQBSJTPO w TFOTJUJWFͳ෦෼͸3FBDUKT͕໘౗Λݟͯ͘ΕΔͷͰɺ'MVYͰΞ ϓϦέʔγϣϯͷॲཧͷྲྀΕΛ୯७ʹɻ

    Slide 55

    Slide 55 text

    %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 }); } });

    Slide 56

    Slide 56 text

    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;

    Slide 57

    Slide 57 text

    "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 }); } ); }, }

    Slide 58

    Slide 58 text

    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); }, });

    Slide 59

    Slide 59 text

    $PODMVTJPO

    Slide 60

    Slide 60 text

    3FBDUKTJTGBTU

    Slide 61

    Slide 61 text

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

    Slide 62

    Slide 62 text

    3FBDUKTJTFBTZ

    Slide 63

    Slide 63 text

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

    Slide 64

    Slide 64 text

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

    Slide 65

    Slide 65 text

    5IBOLZPV