Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Dive into React.js
Search
koba04
February 15, 2015
Programming
10
3.3k
Dive into React.js
歌舞伎座.tech#6「VirtualDOMとReact」での発表資料です。
http://kbkz.connpass.com/event/11254/
koba04
February 15, 2015
Tweet
Share
More Decks by koba04
See All by koba04
フロントエンドの現在地とこれから
koba04
10
5k
Standing on the shoulders of giants
koba04
0
2.8k
React/Next によるアプリケーション開発のこれから
koba04
61
17k
フロントエンド刷新をプロジェクトとして進める際に気をつけていること
koba04
3
1.8k
How useEvent would change our applications
koba04
1
3k
kintoneフロントエンド刷新によるモノリスからの脱却とその先に目指す未来
koba04
3
15k
Make it Declarative with React
koba04
0
1.6k
Ready for React in 2019
koba04
2
1.7k
Algorithms in React
koba04
14
16k
Other Decks in Programming
See All in Programming
DROBEの生成AI活用事例 with AWS
ippey
0
140
Domain-Driven Transformation
hschwentner
2
1.9k
法律の脱レガシーに学ぶフロントエンド刷新
oguemon
5
740
パスキーのすべて ── 導入・UX設計・実装の紹介 / 20250213 パスキー開発者の集い
kuralab
3
830
Unity Android XR入門
sakutama_11
0
170
Grafana Loki によるサーバログのコスト削減
mot_techtalk
1
130
密集、ドキュメントのコロケーション with AWS Lambda
satoshi256kbyte
1
200
Amazon ECS とマイクロサービスから考えるシステム構成
hiyanger
2
580
Rubyで始める関数型ドメインモデリング
shogo_tksk
0
120
Django NinjaによるAPI開発の効率化とリプレースの実践
kashewnuts
1
150
CloudNativePGを布教したい
nnaka2992
0
100
Java Webフレームワークの現状 / java web framework at burikaigi
kishida
9
2.2k
Featured
See All Featured
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
9
500
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
133
33k
The Power of CSS Pseudo Elements
geoffreycrofte
75
5.5k
A Tale of Four Properties
chriscoyier
158
23k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
How STYLIGHT went responsive
nonsquared
98
5.4k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
233
17k
The Art of Programming - Codeland 2020
erikaheidi
53
13k
The Invisible Side of Design
smashingmag
299
50k
Statistics for Hackers
jakevdp
797
220k
Building an army of robots
kneath
303
45k
How GitHub (no longer) Works
holman
314
140k
Transcript
%JWFJOUP3FBDUKT Վب࠲UFDI !LPCB
!LPCB w 8FC"QQMJDBUJPO&OHJOFFS w IUUQLPCBDPN
None
3FBDUKT
http://facebook.github.io/react/
3FBDUKTFWFSZXIFSF
https://www.facebook.com/
http://instagram.com/
http://instagram.com/
https://speakerdeck.com/mridgway/isomorphic-flux
None
http://facebook.github.io/react/docs/videos.html
https://developer.atlassian.com/blog/2015/02/rebuilding-hipchat-with-react/
https://vivaldi.com/
http://techblog.netflix.com/2015/01/netflix-likes-react.html
3FBDUXPSLTXJUIPVUUIF%0. https://github.com/reactjs/react-art/blob/master/src/ReactART.js
http://engineering.flipboard.com/2015/02/mobile-web/
None
$PNQPOFOU
$PNQPOFOU w $PNQPOFOUΛ࡞ͬͯ͏ɻશͯ$PNQPOFOU୯ҐͰߟ͑Δɻ w EJW3FBDUKT͕ఆ͍ٛͯ͠Δ$PNQPOFOUɻ var Hello = React.createClass({ render()
{ return <div>Hello {this.props.name}</div>; } }); React.render(<Hello name=“kbkz” />, document.body); // <div>Hello kbkz</div>
&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>
$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.
1SPQBOE4UBUF
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ͷͷ
1SPQJTB4QFD w ֎෦ͱͷ*'༷ͱͳΔɻ w 1SPQ5ZQFTͰ໌֬ʹఆٛ͢Δɻ var Food = React.createClass({ propTypes:
{ name: React.PropTypes.string.isRequired }, render() { return <ul><li>{this.props.name}</li></ul> } }); จࣈྻͰඞਢ
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͔Ͳ͏͔
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!'); } }
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> ); } });
1SFEJDUBCMF
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” /
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 }); };
3FSFOEFSUIFFOUJSFBQQ w 3FBDUKT͕%0.ͷࠩߋ৽Λͬͯ͘ΕΔͷͰɺΞϓϦͷσʔ λΛ·ͱΊ͓͍ͯ࣋ͬͯͯTFU4UBUFͰߋ৽͢ΔΑ͏ͳࡶͳΞʔ ΩςΫνϟՄೳʹͳΔɻ
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>; }
-JGFDZDMF
$PNQPOFOUMJGFDZDMF w .PVOUJOH w DPNQPOFOU8JMM.PVOU DPNQPOFOU%JE.PVOU w 6QEBUJOH w
DPNQPOFOU8JMM3FDFJWF1SPQT TIPVME$PNQPOFOU6QEBUF DPNQPOFOU8JMM6QEBUF DPNQPOFOU%JE6QEBUF w 6ONPVOUJOH w DPNQPOFOU8JMM6ONPVOU
+49
+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”} />
SFBDUUPPMT w KTYίϚϯυมϥΠϒϥϦ͕ೖ͍ͬͯΔɻ w CSPXTFSJGZͷUSBOTGPSNͰ͋ΔSFBDUJGZɺXFCQBDLͷMPBEFS Ͱ͋ΔKTYMPBEFSͰΘΕ͍ͯΔɻ w IBSNPOZPQUJPOΛ༗ޮʹ͢Δ͜ͱͰҰ෦ͷ&4 GFBUVSFΛ ͏͜ͱ͕ग़དྷΔɻ
#BCFM UP w #BCFMࣗମ͕+49Λαϙʔτ͍ͯ͠ΔͷͰ#BCFM͚ͩͰSFBDU UPPMT͏ඞཁ͕ͳ͍ɻ w IUUQCMPHLPCBDPNQPTUB DPNCJOBUJPOPGSFBDUKTBOEUP w CSPXTFSJGZ
CBCFMJGZɺXFCQBDL CBCFMMPBEFS͚ͩͰ 0,ɻ w +49ͷQBSTFSʹ33FWFSTFSBDPSOKTYΛ͍ͬͯΔɻ
#SPXTFSFOWJSPONFOU
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” />; }
EBOHFSPVTMZ4FU*OOFS)5.- w )5.-Λͦͷ··͍ͨ͠ͱ͖ʹ͏ɻ w ҙͯ͠͏͜ͱΛڧௐͨ͠*'ɻ w ໊લมΘΔ͔ɻ *TTVFͰٞத w
IUUQTHJUIVCDPNGBDFCPPLSFBDUJTTVFT createMarkup(data) { return { __html: someEncodeAPI(data) }; }, render() { return <div dangerouslySetInnerHTML={this.createMarkup(data)} /> }
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
)PXUPUFTU
)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(); });
3PVUJOH
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); });
4FSWFSTJEFSFOEFSJOH
4FSWFSTJEFSFOEFSJOH w 3FBDUKTͰɺ$PNQPOFOUͷঢ়ଶΛΦϒδΣΫτͱͯ࣋ͬ͠ ͍ͯͯڥʹΑΒͣ)5.-ͱͯ͠ు͖ग़͢͜ͱ͕ग़དྷΔɻ w 4&0ɺJOJUJBMMPBEͷͨΊʹɻ w TFSWFSଆͰͷ+49ͷύʔεOPEFKTYΛͬͨΓUP UP OPEFDPNNBOE
ΛͬͨΓɻ w 4UPSFͷσʔλΛ4FSWFSͱ#SPXTFSͰڞ༗͢Δɻ
)PXUPJNQMFNFOU w SFOEFS5P4USJOH w ϑϩϯτଆͰ3FBDUKTΛ͍͍ͨ߹ʹ͏ɻEBUB SFBDUJEͳͲ͕Ճ͞Εͨ)5.-͕ੜ͞ΕΔɻ w ϝΠϯͬͪ͜ɻ w SFOEFS5P4UBUJD.BSLVQ
w EBUBSFBDUJEͳͲ͕͍ͯͳ͍୯७ͳ)5.-Λฦ͢ͷͰ੩త ͳϖʔδΛੜ͍ͨ͠߹ʹ͏ɻ
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Λ࠶ߏங͢Δ
'MVY
'MVYJTBBSDIJUFDUVSF https://github.com/facebook/flux
'MVYJTBBSDIJUFDUVSF w GBDFCPPLqVYʹEJTQBUDIFS͔͠ͳ͍ɻ w ͦͷଞ4UPSF͕&WFOU&NJUUFSܧঝͯ͠Δ͘Β͍ɻ w ΑΓগͳ͍ίʔυͰॻ͖ͨ͘͢͠ΓɺTFSWFSTJEFSFOEFSJOH ʹରԠ͢ΔͨΊʹɺ৭ʑͳ'MVY࣮͕ཚཱ͍ͯ͠Δɻ w IUUQTHJUIVCDPNWPSPOJBOTLJqVYDPNQBSJTPO
w TFOTJUJWFͳ෦3FBDUKT͕໘Λݟͯ͘ΕΔͷͰɺ'MVYͰΞ ϓϦέʔγϣϯͷॲཧͷྲྀΕΛ୯७ʹɻ
%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 }); } });
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;
"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 }); } ); }, }
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); }, });
$PODMVTJPO
3FBDUKTJTGBTU
"EWBOUBHFPG7JSUVBM%0. w ͍ʁ w K2VFSZͳͲͰ%0.৮ͬͯॻ͍ͨํ͕͍ɻ w %0.ΛҙࣝͤͣʹࡶʹൣғͰ$PNQPOFOUΛߋ৽ͯͦ͠ ͍ͦ͜͜ɻ w TIPVME$PNQPOFOU6QEBUFʹΑΔ࠷దԽɻ
3FBDUKTJTFBTZ
.BLFEFWFMPQNFOUFBTJFS w %0.ͷөΛ3FBDUKTʹͤΔ͜ͱͰΞϓϦέʔγϣϯͷ ίʔυΛॻ͘͜ͱʹྗग़དྷΔɻ w +BWB4DSJQUͷࣝΛͦͷ··׆͔ͨ͠։ൃɻ
$PODMVTJPO w 3FBDUKT։ൃΛߴΊΔ͜ͱ͕తͰͳͯ͘ɺΞϓϦ έʔγϣϯͷ৴པੑΛߴΊΔ͜ͱ͕తɻ w 7JFXͷߋ৽Λ3FBDUKTʹ͓ͤͯ͘͜ͱͰΞϓϦέʔγϣϯͷ ίʔυΛγϯϓϧʹॻ͘͜ͱ͕ग़དྷΔɻ w $PNQPOFOUΛ࡞Δ͚ͩͳͷͰϥΠϒϥϦͷαΠζ͋Δͷ ͷಋೖ͍͢͠ɻ
w 3FBDUKTࠓΘΕ͍ͯΔٕज़ɻ
5IBOLZPV