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
410
React速習会@Wantedly
社内でReactを浸透させるために行った速習会の資料
Kento Moriwaki
February 04, 2016
Tweet
Share
More Decks by Kento Moriwaki
See All by Kento Moriwaki
わかった気になれる CRDT を使った共同編集
kentomoriwaki
8
4.1k
デザインシステムを導入してUIに秩序を取り戻す - React (Native)編 #rejectron2018
kentomoriwaki
16
3.6k
ReactでWebとNativeの共通UIライブラリを作ろう
kentomoriwaki
0
1.1k
BFFを導入しなかった理由
kentomoriwaki
4
13k
TypeScript in Wantedly
kentomoriwaki
2
700
5分でわかる React "Suspense"
kentomoriwaki
3
1.4k
導入して1年経ったReact周辺の 技術スタックを反省します | React反省会@Wantedly
kentomoriwaki
10
8.5k
Immutable.jsとReact @Wantedly ~入門編~
kentomoriwaki
8
74k
Other Decks in Technology
See All in Technology
急成長する企業で作った、エンジニアが輝ける制度/ 20250214 Rinto Ikenoue
shift_evolve
3
1.2k
現場で役立つAPIデザイン
nagix
32
12k
自動テストの世界に、この5年間で起きたこと
autifyhq
10
8.4k
アジャイル開発とスクラム
araihara
0
170
偶然 × 行動で人生の可能性を広げよう / Serendipity × Action: Discover Your Possibilities
ar_tama
1
1.1k
滅・サービスクラス🔥 / Destruction Service Class
sinsoku
6
1.6k
ユーザーストーリーマッピングから始めるアジャイルチームと並走するQA / Starting QA with User Story Mapping
katawara
0
200
転生CISOサバイバル・ガイド / CISO Career Transition Survival Guide
kanny
3
960
明日からできる!技術的負債の返済を加速するための実践ガイド~『ホットペッパービューティー』の事例をもとに~
recruitengineers
PRO
3
380
エンジニアが加速させるプロダクトディスカバリー 〜最速で価値ある機能を見つける方法〜 / product discovery accelerated by engineers
rince
0
110
TAMとre:Capセキュリティ編 〜拡張脅威検出デモを添えて〜
fujiihda
1
230
マルチモーダル理解と生成の統合 DeepSeek Janus, etc... / Multimodal Understanding and Generation Integration
hiroga
0
380
Featured
See All Featured
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
47
5.2k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
174
51k
How to Think Like a Performance Engineer
csswizardry
22
1.3k
The Cost Of JavaScript in 2023
addyosmani
47
7.3k
KATA
mclloyd
29
14k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
29
1k
Done Done
chrislema
182
16k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
656
59k
Optimizing for Happiness
mojombo
376
70k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
9
440
Raft: Consensus for Rubyists
vanstee
137
6.8k
How GitHub (no longer) Works
holman
314
140k
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ʹೖΕΔ • ίϯϙʔωϯτͷڍಈ͚ͦͩ͜ΈΕશ ͔ͯΔʂ • αʔόʔαΠυϨϯμϦϯά؆୯