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
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.7k
デザインシステムを導入して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
750
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
やる気のない自分との向き合い方/How to Deal with Your Unmotivated Self
sanogemaru
0
440
フルカイテン株式会社 エンジニア向け採用資料
fullkaiten
0
9.1k
SwiftUIのGeometryReaderとScrollViewを基礎から応用まで学び直す:設計と活用事例
fumiyasac0921
0
150
Escaping_the_Kraken_-_October_2025.pdf
mdalmijn
0
150
AI時代だからこそ考える、僕らが本当につくりたいスクラムチーム / A Scrum Team we really want to create in this AI era
takaking22
7
4k
SREとソフトウェア開発者の合同チームはどのようにS3のコストを削減したか?
muziyoshiz
1
110
小学4年生夏休みの自由研究「ぼくと Copilot エージェント」
taichinakamura
0
550
AI時代こそ求められる設計力- AWSクラウドデザインパターン3選で信頼性と拡張性を高める-
kenichirokimura
3
170
Git in Team
kawaguti
PRO
2
320
"プロポーザルってなんか怖そう"という境界を超えてみた@TSUDOI by giftee Tech #1
shilo113
0
160
PLaMo2シリーズのvLLM実装 / PFN LLM セミナー
pfn
PRO
2
1.1k
カンファレンスに託児サポートがあるということ / Having Childcare Support at Conferences
nobu09
1
460
Featured
See All Featured
[RailsConf 2023] Rails as a piece of cake
palkan
57
5.9k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
248
1.3M
Documentation Writing (for coders)
carmenintech
75
5k
Code Review Best Practice
trishagee
72
19k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
620
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
19
1.2k
How to train your dragon (web standard)
notwaldorf
96
6.3k
The Cult of Friendly URLs
andyhume
79
6.6k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
23
1.5k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
jQuery: Nuts, Bolts and Bling
dougneiner
64
7.9k
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ʹೖΕΔ • ίϯϙʔωϯτͷڍಈ͚ͦͩ͜ΈΕશ ͔ͯΔʂ • αʔόʔαΠυϨϯμϦϯά؆୯