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
React速習会@Wantedly
Search
Kento Moriwaki
February 04, 2016
Technology
1
430
React速習会@Wantedly
社内でReactを浸透させるために行った速習会の資料
Kento Moriwaki
February 04, 2016
Tweet
Share
More Decks by Kento Moriwaki
See All by Kento Moriwaki
わかった気になれる CRDT を使った共同編集
kentomoriwaki
8
4.5k
デザインシステムを導入してUIに秩序を取り戻す - React (Native)編 #rejectron2018
kentomoriwaki
16
3.7k
ReactでWebとNativeの共通UIライブラリを作ろう
kentomoriwaki
0
1.2k
BFFを導入しなかった理由
kentomoriwaki
4
13k
TypeScript in Wantedly
kentomoriwaki
2
740
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
はじめての転職講座/The Guide of First Career Change
kwappa
1
960
LLMで構造化出力の成功率をグンと上げる方法
keisuketakiguchi
0
810
React Server ComponentsでAPI不要の開発体験
polidog
PRO
0
240
Oracle Cloud Infrastructure:2025年7月度サービス・アップデート
oracle4engineer
PRO
1
190
【OptimizationNight】数理最適化のラストワンマイルとしてのUIUX
brainpadpr
2
480
2025新卒研修・HTML/CSS #弁護士ドットコム
bengo4com
3
13k
事業特性から逆算したインフラ設計
upsider_tech
0
100
ユーザー課題を愛し抜く――AI時代のPdM価値
kakehashi
PRO
1
120
家族の思い出を形にする 〜 1秒動画の生成を支えるインフラアーキテクチャ
ojima_h
3
1.1k
Eval-Centric AI: Agent 開発におけるベストプラクティスの探求
asei
0
120
Rubyの国のPerlMonger
anatofuz
3
740
OPENLOGI Company Profile for engineer
hr01
1
38k
Featured
See All Featured
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
26k
Practical Orchestrator
shlominoach
190
11k
Fashionably flexible responsive web design (full day workshop)
malarkey
407
66k
Product Roadmaps are Hard
iamctodd
PRO
54
11k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
332
22k
Designing Experiences People Love
moore
142
24k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
10
1k
What’s in a name? Adding method to the madness
productmarketing
PRO
23
3.6k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
251
21k
KATA
mclloyd
32
14k
Unsuck your backbone
ammeep
671
58k
YesSQL, Process and Tooling at Scale
rocio
173
14k
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ʹೖΕΔ • ίϯϙʔωϯτͷڍಈ͚ͦͩ͜ΈΕશ ͔ͯΔʂ • αʔόʔαΠυϨϯμϦϯά؆୯