Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
React速習会@Wantedly
Search
Kento Moriwaki
February 04, 2016
Technology
1
440
React速習会@Wantedly
社内でReactを浸透させるために行った速習会の資料
Kento Moriwaki
February 04, 2016
Tweet
Share
More Decks by Kento Moriwaki
See All by Kento Moriwaki
わかった気になれる CRDT を使った共同編集
kentomoriwaki
8
4.9k
デザインシステムを導入してUIに秩序を取り戻す - React (Native)編 #rejectron2018
kentomoriwaki
16
3.8k
ReactでWebとNativeの共通UIライブラリを作ろう
kentomoriwaki
0
1.2k
BFFを導入しなかった理由
kentomoriwaki
4
13k
TypeScript in Wantedly
kentomoriwaki
2
760
5分でわかる React "Suspense"
kentomoriwaki
3
1.5k
導入して1年経ったReact周辺の 技術スタックを反省します | React反省会@Wantedly
kentomoriwaki
10
8.7k
Immutable.jsとReact @Wantedly ~入門編~
kentomoriwaki
8
75k
Other Decks in Technology
See All in Technology
IPv6-mostly field report from RubyKaigi 2026
sorah
0
230
今すぐGoogle Antigravityを触りましょう
rfdnxbro
0
230
小規模チームによる衛星管制システムの開発とスケーラビリティの実現
sankichi92
0
160
Bedrock のコスト監視設計
fohte
2
250
プロダクト負債と歩む持続可能なサービスを育てるための挑戦
sansantech
PRO
1
1.1k
adk-samples に学ぶデータ分析 LLM エージェント開発
na0
3
910
【保存版】「ガチャ」からの脱却:Gemini × Veoで作る、意図を反映するAI動画制作ワークフロー
nekoailab
0
120
段階的に進める、 挫折しない自宅サーバ入門
yu_kod
4
1.8k
ローカルLLM基礎知識 / local LLM basics 2025
kishida
25
11k
その意思決定、まだ続けるんですか? ~痛みを超えて未来を作る、AI時代の撤退とピボットの技術~
applism118
45
24k
DGX SparkでローカルLLMをLangChainで動かした話
ruzia
0
130
Data Hubグループ 紹介資料
sansan33
PRO
0
2.3k
Featured
See All Featured
VelocityConf: Rendering Performance Case Studies
addyosmani
333
24k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
9
990
How To Stay Up To Date on Web Technology
chriscoyier
791
250k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
22k
Mobile First: as difficult as doing things right
swwweet
225
10k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
680
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
2.6k
Fireside Chat
paigeccino
41
3.7k
Producing Creativity
orderedlist
PRO
348
40k
Agile that works and the tools we love
rasmusluckow
331
21k
Reflections from 52 weeks, 52 projects
jeffersonlam
355
21k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
10
690
Transcript
Reactशձ@Wantedly 2016/02/04
୭ʁ • Kento Moriwaki • 2015य़ɺ৽ଔೖࣾ • ϑϩϯτΤϯυ͖ • Angular৮ͬͯͨ
ࠓΔ͜ͱ • React৮ͬͯΈΔ • Flux(Redux)ͯ͠ΈΔ • αʔόʔαΠυͰϨϯμϦϯάͯ͠ΈΔ • ଞͷใ
४උ git clone
[email protected]
:KentoMoriwaki/react_sokushu.git cd react_sokushu npm install npm install
-g webpack • Nodeݹ͗͢Δͱಈ͔ͳ͍͔ • v5.3.0Ͱ֬ೝ
४උ • Chrome dev toolͰReactͷσόοά͕ग़དྷΔ https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi
Reactͱ(Α͘ฉ͘) • Facebook͕։ൃ • UIͷͨΊͷϥΠϒϥϦ • ϑϨʔϜϫʔΫͰͳ͍ • ԾDOM •
JSX
ͱΓ͋͑ͣಈ͔͢ git checkout component npm start webpack —watch • http://localhost:3000ʹΞΫηε
• ೖྗͰ͖ͳ͍ϑΥʔϜ͕දࣔ͞ΕΔ
components/App.js import React, { Component } from 'react' export default
class App extends Component { constructor(props) { super(props) this.state = { title: '', description: '' } } render() { return ( <form> <fieldset> <legend>Create Issue</legend> <input type="text" value={this.state.title} placeholder="Input title" /> <textarea value={this.state.description} placeholder="Input description" /> <button>Save</button> </fieldset> </form> ) } }
Component • Reactͷओ • View + Controller • ViewJSXͰɺrenderϝιουʹهड़͢Δ •
this.stateʹঢ়ଶΛอ࣋͢Δ
One-way data binding • ೖྗͯ͠σʔλมΘΒͳ͍ • ࣗͰ໌ࣔతʹมߋ͠ͳ͍ͱɺϏϡʔม ΘΒͳ͍ • σʔλ͕มΘΔͱϏϡʔ͕ࣗಈతʹมΘΔ
• this.setState(newState)Ͱมߋ
One-way data binding • onChange={} ʹϋϯυϥΛॻ͍ͯঢ়ଶΛมߋ͢Δ onChangeTitle(e) { this.setState({ title:
e.target.value }) } onChangeDescription(e) { this.setState({ description: e.target.value }) } render() { return ( <form> <fieldset> <legend>Create Issue</legend> <input type="text" value={this.state.title} onChange={this.onChangeTitle.bind(this)} /> <textarea value={this.state.description} onChange={this.onChangeDescription.bind(this)} /> <button>Save</button> </fieldset> </form> ) }
One-way data binding • σʔλ͕Ұํʹ͔͠ྲྀΕͳ͍ • ߟ͑Δ͜ͱ͕γϯϓϧʹͳΔ • σʔλ͔ΒͲ͏ͬͯϏϡʔΛ࡞Δ͔ •
σʔλΛͲ͏มߋ͢Δ͔
ෳίϯϙʔωϯτ • ೖྗͨ͠σʔλΛϦετͰද͍ࣔͨ͠ • git checkout list • ͖ͬ͞ͷAppΛIssueFormʹ •
৽͘͠IssueListίϯϙʔωϯτΛ࡞
ෳίϯϙʔωϯτ
components/App.js • IssueFormͰ࡞ΒΕͨΦϒδΣΫτΛIssueList ʹ͍ͨ͠ import React, { Component } from
'react' import IssueList from './IssueList' import IssueForm from './IssueForm' export default class App extends Component { render() { return ( <div> <IssueList /> <IssueForm /> </div> ) } }
Property • ίϯϙʔωϯτϓϩύςΟΛ࣋ͭ • JSXͷଐੑͱͯ͠ड͚औΕΔ • <IssueList issues={this.state.issues} /> •
ܕͷఆٛͳͲ͕ߦ͑Δ • PropTypesͱݺΕɺͲ͏͍͏ଐੑ໊ͰͲ͏͍͏ܕ ͷσʔλΛड͚औΔ͔ఆٛͰ͖Δ
components/App.js • IssueList͕issuesϓϩύςΟΛड͚औΔ export default class App extends Component {
constructor(props) { super(props) this.state = { issues: [ { id: 1, title: 'First Issue', description: 'foobarbaz' }, { id: 2, title: 'Incident', description: 'OMG' } ] } } render() { return ( <div> <IssueList issues={this.state.issues} /> <IssueForm /> </div> ) } }
components/App.js • IssueForm͕onSubmitϓϩύςΟΛड͚औΔ export default class App extends Component {
onSubmit(issue) { let issues = this.state.issues issue.id = issues.length + 1 this.setState({ issues: issues.concat(issue) }) } render() { return ( <div> <IssueList issues={this.state.issues} /> <IssueForm onSubmit={this.onSubmit.bind(this)} /> </div> ) } }
components/IssueList.js • this.props Ͱ͞ΕͨଐੑΛࢀরͰ͖Δ export default class IssueList extends Component
{ render() { return ( <table> { this.props.issues.map((issue) => { return ( <tr key={issue.id}> <td>{issue.id}</td> <td>{issue.title}</td> <td>{issue.description}</td> </tr> ) }) } </table> ) } }
components/IssueList.js • this.props Ͱ͞ΕͨଐੑΛࢀরͰ͖Δ export default class IssueList extends Component
{ constructor(props) { super(props) this.state = { title: '', description: '' } } onSubmit(e) { e.preventDefault() this.props.onSubmit(this.state) } render() { return ( <form onSubmit={this.onSubmit.bind(this)}> <fieldset> <legend>Create Issue</legend> <input type="text" value={this.state.title} …/> <textarea value={this.state.description} … /> <button>Save</button> </fieldset> </form> ) } }
StateͱProperty • StateMutable • PropertyImmutable • StateΛࢠComponentͷPropertyͱͯ͢͠ • ࢠComponent͔ΒState͕มߋ͞ΕΔ͜ͱͳ͍ •
มߋίʔϧόοΫͰ
Flux
Fluxͱ • Facebook͕ఏএͨ͠ΞʔΩςΫνϟ • ΞϓϦέʔγϣϯͱͯ͠σʔλϑϩʔҰํ ͚ͩʹྲྀΕΔ
MVC • Model͕ViewΛ࡞ΓɺView͕ModelΛมߋ͠ɺ͞Βʹ View͕มߋ͞ΕΔ
Flux • Action͕StoreΛมߋ͠ɺStore͕ViewΛͭ͘ΓɺView͕ ActionΛൃߦ͢Δ
ΣϒαΠτͱࣅͯΔ • యܕతͳΣϒαΠτ • αʔόʔ͕ϦΫΤετΛड͚औΓɺ DBͷσʔλΛߋ৽͢Δ • ৽͍͠σʔλΛݩʹϖʔδΛҰ͔ΒϨϯμϦϯά͢Δ • Flux
• Dispatcher͕ActionΛड͚औΓɺStoreΛߋ৽͢Δ • ৽͍͠StoreΛݩʹϖʔδΛҰ͔ΒϨϯμϦϯά͢Δ • ຊReact্͕ख͍͜ͱඞཁͳ෦͚ͩϨϯμϦϯάͯ͘͠ΕΔ
Flux࣮ • ΞʔΩςΫνϟͳͷͰ࣮͍Ζ͍Ζ • MVCϑϨʔϜϫʔΫ͕͍ͬͺ͍ଘࡏ͢ΔΈ ͍ͨʹ • ࠷ۙਓؾͷReduxΛ৮ͬͯΈΔ
Redux • Flux࣮ͷ̍ͭ • ಛ • ΞϓϦέʔγϣϯͷঢ়ଶΛ̍ͭͰѻ͏ • ঢ়ଶread-only •
มߋ७ਮͳؔͰॻ͚Δ • git checkout flux
Action • ΞΫγϣϯ໊ͱҾΛ·ͱΊͨΦϒδΣΫτ • ActionCreator • ActionΛฦؔ͢ export const ADD
= 'ADD_ISSUE' export const REFRESH = 'REFRESH_ISSUES' export function addIssue(issue) { return { type: ADD, issue: issue } } export function refreshIssues(issues) { return { type: REFRESH, issues: issues } }
Reducer • Actionͱݱࡏͷঢ়ଶΛड͚ͯɺ৽͍͠ঢ়ଶΛฦؔ͢ const initialState = [ { id: 1,
title: 'First issue', author: {id: 1}, assignee: {id: 1} } ] export default function issues(state, action) { if (typeof state == 'undefined') { return initialState } switch (action.type) { case ADD: let issue = action.issue return [ ...state, { id: state.length + 1, title: issue.title, description: issue.description } ] case REFRESH: return action.issues } return state }
Container • ಛผͳComponent • ReduxͱReactΛܨ͛Δଘࡏ • ҎԼͷͷΛpropertyͱͯ͠ड͚औΕΔΑ͏ʹͳΔ • ReducerʹΑͬͯ࡞ΒΕΔঢ়ଶ(store) •
ActionΛൃߦ͢ΔͨΊͷdispatchؔ
Container import React, { Component } from 'react' import {
bindActionCreators } from 'redux' import { addIssue, loadIssues } from '../actions/issues' class App extends Component { onAdd(issue) { this.props.dispatch(addIssue(issue)) } render() { const { dispatch, issues } = this.props return ( <div> <IssueList issues={issues} onAdd={this.onAdd.bind(this)} /> </div> ) } } function mapStateToProps(state) { return { issues: state.issues } } export default connect(mapStateToProps)(App)
Container issuesͱdispatch͕͞Ε͍ͯΔ
Fluxͯ͠ΈΔ • git checkout flux • saveͯ͠Ճ͞Εͳ͍ϑΥʔϜ͕͋Δ • TODO •
reducers/issues.js • containers/App.js
reducers/issues.js • ΞΫγϣϯ͕ൃߦ͞Εͨͱ͖ʹstate͕Ͳ͏ม ΘΔ͔ͷ͕ؔ͋Δ͚ͩ import { ADD, REFRESH } from
'../actions/issues' const initialState = [ { id: 1, title: 'First issue', author: {id: 1}, assignee: {id: 1} } ] export default function issues(state, action) { if (typeof state == 'undefined') { return initialState } switch (action.type) { case ADD: //TODO: Add new issue } return state }
reducers/issues.js • ADDΞΫγϣϯͰɺstateʹissueΛՃͨ͠৽ ͍͠stateΛฦ͍͍ͤ import { ADD, REFRESH } from
'../actions/issues' const initialState = [ { id: 1, title: 'First issue', author: {id: 1}, assignee: {id: 1} } ] export default function issues(state, action) { if (typeof state == 'undefined') { return initialState } switch (action.type) { case ADD: //TODO: Add new issue } return state }
reducers/issues.js • ৽͍͠ྻΛฦ͢ • stateʹೖ͍͚ͯ͠ͳ͍ case ADD: let issue =
action.issue return [ ...state, { id: state.length + 1, title: issue.title, description: issue.description } ]
containers/App.js • ϑΥʔϜͷίʔϧόοΫͰɺActionΛൃߦ͠ ͍ͨ import React, { Component } from
'react' import { connect } from 'react-redux' import IssueList from '../components/IssueList' import { addIssue } from '../actions/issues' class App extends Component { onAdd(issue) { //TODO: Dispatch addIssue action! } render() { return ( <div> <IssueList issues={this.props.issues} onAdd={this.onAdd.bind(this)} /> </div> ) } }
containers/App.js • addIssueؔͰฦ͞ΕΔΞΫγϣϯΛɺ this.props.dispatchʹ͢ import IssueList from '../components/IssueList' import {
addIssue } from '../actions/issues' class App extends Component { onAdd(issue) { this.props.dispatch(addIssue(issue)) } render() { return ( <div> <IssueList issues={this.props.issues} onAdd={this.onAdd.bind(this)} /> </div> ) } }
Ajax • αʔόʔ͔ΒσʔλΛऔಘ͍ͨ͠ • ActionͰΔͷ͕Ұൠత • redux-thunk ͱ͍͏middlewareΛ͏ • git
checkout ajax
Ajax • ActionCreatorͰdispatchΛड͚ΔؔΛฦ͢ export const ADD = 'ADD_ISSUE' export const
LOAD = 'LOAD_ISSUES' export const REFRESH = 'REFRESH_ISSUES' export function addIssue(issue) { return { type: ADD, issue: issue } } export function refreshIssues(issues) { return { type: REFRESH, issues: issues } } export function loadIssues() { return dispatch => { fetch('/api/issues') .then(res => res.json()) .then(json => { dispatch(refreshIssues(json)) }) } }
Ajax • ड͚औͬͨdispatchΛඇಉظͰ࣮ߦ͢Δ export const ADD = 'ADD_ISSUE' export const
LOAD = 'LOAD_ISSUES' export const REFRESH = 'REFRESH_ISSUES' export function addIssue(issue) { return { type: ADD, issue: issue } } export function refreshIssues(issues) { return { type: REFRESH, issues: issues } } export function loadIssues() { return dispatch => { fetch('/api/issues') .then(res => res.json()) .then(json => { dispatch(refreshIssues(json)) }) } }
Ajax • Reducer import { ADD, REFRESH } from '../actions/issues'
export default function issues(state, action) { if (typeof state == 'undefined') { return initialState } switch (action.type) { case ADD: let issue = action.issue return [ ...state, { id: state.length + 1, title: issue.title, description: issue.description } ] case REFRESH: return action.issues } return state }
αʔόʔͰϨϯμϦϯάͯ͠ ΈΔ
αʔόʔαΠυϨϯμϦϯά • SEOͷ؍ • Ϋϩʔϥʔ͕Ͳ͜·ͰJavaScriptΛ্ख͘ѻ͍͑ͯΔ͔ෆ҆ • ੩తͳϖʔδΛฦ͍ͨ͠ • Ϣʔβʔମݧ •
ϖʔδʹΞΫηε͔ͯ͠ΒɺReactͷ࣮ߦΛͬͯɺAPIϦ ΫΤετૹͬͯɺඳը͞ΕΔͷ͕ɺ͍
Έ • DOM৮ͬͯͳ͍ͷͰɺαʔόʔଆͰ࣮ߦͰ͖ΔJS ʹͳ͍ͬͯΔ • ΞϓϦέʔγϣϯͷঢ়ଶ͕ܾ·ΕɺϏϡʔܾ· Δ • DOMͱͯ͠Ͱͳ͘ɺจࣈྻͱͯ͠Ϩϯμʔ͢Δ •
ͦͷঢ়ଶͷΦϒδΣΫτΛಉ࣌ʹฦ͢
ͬͯΈΔ • git checkout ssr • npm start ͷ࠶ىಈ
server.js • client.jsͱେମಉ͡ • renderToStringͰHTMLจࣈ ྻ͕࡞ΒΕΔ //TODO: Interpolate html and
initialState function renderFullPage(html, initialState) { return … } function handler(req, res) { const store = createStore(issueApp) //TOOD: Set initial data const html = renderToString( <Provider store={store}> <App /> </Provider> ) const initialState = store.getState() res.send(renderFullPage(html, initialState)) } app.use(Express.static('static')) app.use('/api/issues', issuesHandler) app.use(handler) app.listen(port)
server.js • renderFullPageͰrenderToString͞ΕͨHTML ͱɺॳظstateΛςϯϓϨʔτʹૠೖ͢Δ͚ͩ //TODO: Interpolate html and initialState function
renderFullPage(html, initialState) { return ` <!doctype html> <html> <head> <title>React Sokushu</title> </head> <body> <div id="root"></div> <script src="/dist/bundle.js"></script> </body> </html> ` }
server.js • renderFullPageͰrenderToString͞ΕͨHTML ͱɺॳظstateΛςϯϓϨʔτʹૠೖ͢Δ͚ͩ function renderFullPage(html, initialState) { return `
<!doctype html> <html> <head> <title>React Sokushu</title> </head> <body> <div id="root">${html}</div> <script> window.__INITIAL_STATE__ = ${JSON.stringify(initialState)} </script> <script src="/dist/bundle.js"></script> </body> </html> ` }
view-source
client.js • ঢ়ଶ͕Viewͱҧ͏ͱαΠϨϯμʔ͞ΕΔͷ Ͱɺॳظstate͔ΒstoreΛͭ͘ΔΑ͏ʹ͢Δ const store = createStore( issueApp, window.__INITIAL_STATE__,
applyMiddleware(thunk) ) render( <Provider store={store}> <App /> </Provider>, document.getElementById("root") )
CSS in React
CSS in React • CSSͷ • ໊લͷিಥ • Ϋϥε໊Λ͍ͬͺ͍ߟ͑ͳ͍ͱ •
ΘΕͳ͘ͳͬͨίʔυ͕ফͤͳ͍ • ංେԽ͢Δcss • JSXͰϏϡʔΛ.jsͰॻ͘Α͏ʹͳͬͨͷͰɺελΠϧҰॹʹॻ͖ ͍ͨ
CSS in React • ΫϥεΛ͚ΔΘΓʹinlineͰॻ͘ • style={this.styles.base} Έ͍ͨʹ • ܧঝͱ͔ਏ͍
• :hoverͱ͔͔͚ͳ͍
Radium • coreͰΓͳ͍ػೳΛՃ • https://github.com/FormidableLabs/radium • :hoverͰ͖Δ • ܧঝͰ͖Δ
ॻ͍ͯΈΔ • git checkout css • IssueForm͕ͪΐͬͱ͚ͩ៉ྷʹ
components/IssueForm.js import React, { Component } from 'react' import Radium
from 'radium' import color from 'color' class IssueForm extends Component { render() { return ( <div> <form onSubmit={this.onSubmit.bind(this)}> <fieldset> <legend>Create Issue</legend> <input style={styles.input} type="text" value={this.state.title} …/> <textarea style={styles.input} value={this.state.description} … /> <button style={styles.button}>Save</button> </fieldset> </form> </div> ) } } const styles = { input: { display: 'block', width: '200px', padding: '6px 10px', margin: '10px 0', border: '1px solid #ccc' }, button: { backgroundColor: '#00A4BB', border: 'none', padding: '6px 15px', color: 'white', cursor: 'pointer', } } export default Radium(IssueForm)
components/IssueForm.js class IssueForm extends Component { render() { return (
<div> <form onSubmit={this.onSubmit.bind(this)}> <fieldset> <legend>Create Issue</legend> <input style={styles.input} type="text" value={this.state.title} …/> <textarea style={styles.input} value={this.state.description} … /> <button style={styles.button}>Save</button> </fieldset> </form> </div> ) } } inlineͰ͍ͯͯ͘ దͳΫϥε໊ߟ͑ΔΑΓ؆୯
components/IssueForm.js const styles = { input: { display: 'block', width:
'200px', padding: '6px 10px', margin: '10px 0', border: '1px solid #ccc' }, button: { backgroundColor: '#00A4BB', border: 'none', padding: '6px 15px', color: 'white', cursor: 'pointer', } } ΦϒδΣΫτͰهड़
ͬͯΈΔ • :hoverͰϘλϯͷ৭Λม͑Δ • textareaͷߴ͞Λม͑Δ
components/IssueForm.js import React, { Component } from 'react' import Radium
from 'radium' import color from 'color' const styles = { input: { display: 'block', width: '200px', padding: '6px 10px', margin: '10px 0', border: '1px solid #ccc' }, textarea: { height: '100px' }, button: { backgroundColor: '#00A4BB', border: 'none', padding: '6px 15px', color: 'white', cursor: 'pointer', ':hover': { backgroundColor: color('#00A4BB').darken(0.2).hexString(), } } }
components/IssueForm.js • mouseenter, mouseleaveͰؤு͚ͬͯସ͑ ͯ͘Δ const styles = { button:
{ backgroundColor: '#00A4BB', border: 'none', padding: '6px 15px', color: 'white', cursor: 'pointer', ':hover': { backgroundColor: color('#00A4BB').darken(0.2).hexString(), } } }
components/IssueForm.js • ྻͰࢦఆ͢Δͱɺoverrideͯ͘͠ΕΔ • style={[styles.input, styles.textarea]} class IssueForm extends Component
{ render() { return ( <div> <form onSubmit={this.onSubmit.bind(this)}> <fieldset> <legend>Create Issue</legend> <input style={[styles.input]} type="text" value={this.state.title} … /> <textarea style={[styles.input, styles.textarea]} value={this.state.description} … /> <button style={[styles.button]}>Save</button> </fieldset> </form> </div> ) } }
components/IssueForm.js • ྻͰࢦఆ͢Δͱɺoverrideͯ͘͠ΕΔ • style={[styles.input, styles.textarea]} class IssueForm extends Component
{ render() { return ( <div> <form onSubmit={this.onSubmit.bind(this)}> <fieldset> <legend>Create Issue</legend> <input style={[styles.input]} type="text" value={this.state.title} … /> <textarea style={[styles.input, styles.textarea]} value={this.state.description} … /> <button style={[styles.button]}>Save</button> </fieldset> </form> </div> ) } }
·ͱΊ • ΫϥΠΞϯταΠυͷॻ͖ํ͕େ͖͘มΘΔ • ϏϡʔCSSjsʹೖΕΔ • ίϯϙʔωϯτͷڍಈ͚ͦͩ͜ΈΕશ ͔ͯΔʂ • αʔόʔαΠυϨϯμϦϯά؆୯