Upgrade to Pro — share decks privately, control downloads, hide ads and more …

React速習会@Wantedly

 React速習会@Wantedly

社内でReactを浸透させるために行った速習会の資料

20451aa93aace2f5279f5ea2e7a9c89d?s=128

Kento Moriwaki

February 04, 2016
Tweet

Transcript

  1. React଎शձ@Wantedly 2016/02/04

  2. ୭ʁ • Kento Moriwaki • 2015೥य़ɺ৽ଔೖࣾ • ϑϩϯτΤϯυ޷͖ • Angular৮ͬͯͨ

  3. ࠓ೔΍Δ͜ͱ • React৮ͬͯΈΔ • Flux(Redux)ͯ͠ΈΔ • αʔόʔαΠυͰϨϯμϦϯάͯ͠ΈΔ • ଞͷ৘ใ

  4. ४උ git clone git@github.com:KentoMoriwaki/react_sokushu.git cd react_sokushu npm install npm install

    -g webpack • Node͸ݹ͗͢Δͱಈ͔ͳ͍͔΋ • v5.3.0Ͱ֬ೝ
  5. ४උ • Chrome dev toolͰReactͷσόοά͕ग़དྷΔ https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi

  6. Reactͱ͸(Α͘ฉ͘࿩) • Facebook͕։ൃ • UIͷͨΊͷϥΠϒϥϦ • ϑϨʔϜϫʔΫͰ͸ͳ͍ • Ծ૝DOM •

    JSX
  7. ͱΓ͋͑ͣಈ͔͢ git checkout component npm start webpack —watch • http://localhost:3000ʹΞΫηε

    • ೖྗͰ͖ͳ͍ϑΥʔϜ͕දࣔ͞ΕΔ
  8. 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> ) } }
  9. Component • Reactͷओ໾ • View + Controller • View͸JSXͰɺrenderϝιουʹهड़͢Δ •

    this.stateʹঢ়ଶΛอ࣋͢Δ
  10. One-way data binding • ೖྗͯ͠΋σʔλ͸มΘΒͳ͍ • ࣗ෼Ͱ໌ࣔతʹมߋ͠ͳ͍ͱɺϏϡʔ͸ม ΘΒͳ͍ • σʔλ͕มΘΔͱϏϡʔ͕ࣗಈతʹมΘΔ

    • this.setState(newState)Ͱมߋ
  11. 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> ) }
  12. One-way data binding • σʔλ͕Ұํ޲ʹ͔͠ྲྀΕͳ͍ • ߟ͑Δ͜ͱ͕γϯϓϧʹͳΔ • σʔλ͔ΒͲ͏΍ͬͯϏϡʔΛ࡞Δ͔ •

    σʔλΛͲ͏มߋ͢Δ͔
  13. ෳ਺ίϯϙʔωϯτ • ೖྗͨ͠σʔλΛϦετͰද͍ࣔͨ͠ • git checkout list • ͖ͬ͞ͷAppΛIssueFormʹ •

    ৽͘͠IssueListίϯϙʔωϯτΛ࡞੒
  14. ෳ਺ίϯϙʔωϯτ

  15. 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> ) } }
  16. Property • ίϯϙʔωϯτ͸ϓϩύςΟΛ࣋ͭ • JSXͷଐੑͱͯ͠ड͚औΕΔ • <IssueList issues={this.state.issues} /> •

    ܕͷఆٛͳͲ͕ߦ͑Δ • PropTypesͱݺ͹ΕɺͲ͏͍͏ଐੑ໊ͰͲ͏͍͏ܕ ͷσʔλΛड͚औΔ͔ఆٛͰ͖Δ
  17. 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> ) } }
  18. 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> ) } }
  19. 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> ) } }
  20. 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> ) } }
  21. StateͱProperty • State͸Mutable • Property͸Immutable • StateΛࢠComponentͷPropertyͱͯ͠౉͢ • ࢠComponent͔ΒState͕มߋ͞ΕΔ͜ͱ͸ͳ͍ •

    มߋ͸ίʔϧόοΫͰ
  22. Flux

  23. Fluxͱ͸ • Facebook͕ఏএͨ͠ΞʔΩςΫνϟ • ΞϓϦέʔγϣϯͱͯ͠σʔλϑϩʔ͸Ұํ ޲͚ͩʹྲྀΕΔ

  24. MVC • Model͕ViewΛ࡞ΓɺView͕ModelΛมߋ͠ɺ͞Βʹ View͕มߋ͞ΕΔ

  25. Flux • Action͕StoreΛมߋ͠ɺStore͕ViewΛͭ͘ΓɺView͕ ActionΛൃߦ͢Δ

  26. ΢ΣϒαΠτͱࣅͯΔ • యܕతͳ΢ΣϒαΠτ • αʔόʔ͕ϦΫΤετΛड͚औΓɺ DBͷσʔλΛߋ৽͢Δ • ৽͍͠σʔλΛݩʹϖʔδΛҰ͔ΒϨϯμϦϯά͢Δ • Flux

    • Dispatcher͕ActionΛड͚औΓɺStoreΛߋ৽͢Δ • ৽͍͠StoreΛݩʹϖʔδΛҰ͔ΒϨϯμϦϯά͢Δ • ຊ౰͸React্͕ख͍͜ͱඞཁͳ෦෼͚ͩϨϯμϦϯάͯ͘͠ΕΔ
  27. Flux࣮૷ • ΞʔΩςΫνϟͳͷͰ࣮૷͸͍Ζ͍Ζ • MVCϑϨʔϜϫʔΫ͕͍ͬͺ͍ଘࡏ͢ΔΈ ͍ͨʹ • ࠷ۙਓؾͷReduxΛ৮ͬͯΈΔ

  28. Redux • Flux࣮૷ͷ̍ͭ • ಛ௃ • ΞϓϦέʔγϣϯͷঢ়ଶΛ̍ͭͰѻ͏ • ঢ়ଶ͸read-only •

    มߋ͸७ਮͳؔ਺Ͱॻ͚Δ • git checkout flux
  29. 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 } }
  30. 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 }
  31. Container • ಛผͳComponent • ReduxͱReactΛܨ͛Δଘࡏ • ҎԼͷ΋ͷΛpropertyͱͯ͠ड͚औΕΔΑ͏ʹͳΔ • ReducerʹΑͬͯ࡞ΒΕΔঢ়ଶ(store) •

    ActionΛൃߦ͢ΔͨΊͷdispatchؔ਺
  32. 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)
  33. Container issuesͱdispatch͕౉͞Ε͍ͯΔ

  34. Fluxͯ͠ΈΔ • git checkout flux • saveͯ͠΋௥Ճ͞Εͳ͍ϑΥʔϜ͕͋Δ • TODO •

    reducers/issues.js • containers/App.js
  35. 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 }
  36. 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 }
  37. reducers/issues.js • ৽͍͠഑ྻΛฦ͢ • stateʹ୅ೖͯ͠͸͍͚ͳ͍ case ADD: let issue =

    action.issue return [ ...state, { id: state.length + 1, title: issue.title, description: issue.description } ]
  38. 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> ) } }
  39. 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> ) } }
  40. Ajax • αʔόʔ͔ΒσʔλΛऔಘ͍ͨ͠ • ActionͰ΍Δͷ͕Ұൠత • redux-thunk ͱ͍͏middlewareΛ࢖͏ • git

    checkout ajax
  41. 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)) }) } }
  42. 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)) }) } }
  43. 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 }
  44. αʔόʔͰϨϯμϦϯάͯ͠ ΈΔ

  45. αʔόʔαΠυϨϯμϦϯά • SEOͷ؍఺ • Ϋϩʔϥʔ͕Ͳ͜·ͰJavaScriptΛ্ख͘ѻ͍͑ͯΔ͔ෆ҆ • ੩తͳϖʔδΛฦ͍ͨ͠ • Ϣʔβʔମݧ •

    ϖʔδʹΞΫηε͔ͯ͠ΒɺReactͷ࣮ߦΛ଴ͬͯɺAPIϦ ΫΤετૹͬͯɺඳը͞ΕΔͷ͕ɺ஗͍
  46. ࢓૊Έ • DOM৮ͬͯͳ͍ͷͰɺαʔόʔଆͰ΋࣮ߦͰ͖ΔJS ʹͳ͍ͬͯΔ • ΞϓϦέʔγϣϯͷঢ়ଶ͕ܾ·Ε͹ɺϏϡʔ͸ܾ· Δ • DOMͱͯ͠Ͱ͸ͳ͘ɺจࣈྻͱͯ͠Ϩϯμʔ͢Δ •

    ͦͷঢ়ଶͷΦϒδΣΫτΛಉ࣌ʹฦ͢
  47. ΍ͬͯΈΔ • git checkout ssr • npm start ͷ࠶ىಈ

  48. 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)
  49. 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> ` }
  50. 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> ` }
  51. view-source

  52. client.js • ঢ়ଶ͕Viewͱҧ͏ͱαΠϨϯμʔ͞ΕΔͷ Ͱɺॳظstate͔ΒstoreΛͭ͘ΔΑ͏ʹ͢Δ const store = createStore( issueApp, window.__INITIAL_STATE__,

    applyMiddleware(thunk) ) render( <Provider store={store}> <App /> </Provider>, document.getElementById("root") )
  53. CSS in React

  54. CSS in React • CSSͷ໰୊఺ • ໊લͷিಥ • Ϋϥε໊Λ͍ͬͺ͍ߟ͑ͳ͍ͱ •

    ࢖ΘΕͳ͘ͳͬͨίʔυ͕ফͤͳ͍ • ංେԽ͢Δcss • JSXͰϏϡʔΛ.jsͰॻ͘Α͏ʹͳͬͨͷͰɺελΠϧ΋Ұॹʹॻ͖ ͍ͨ
  55. CSS in React • ΫϥεΛ෇͚Δ୅ΘΓʹinlineͰॻ͘ • style={this.styles.base} Έ͍ͨʹ • ܧঝͱ͔ਏ͍

    • :hoverͱ͔͔͚ͳ͍
  56. Radium • coreͰ͸෺଍Γͳ͍ػೳΛ௥Ճ • https://github.com/FormidableLabs/radium • :hoverͰ͖Δ • ܧঝͰ͖Δ

  57. ॻ͍ͯΈΔ • git checkout css • IssueForm͕ͪΐͬͱ͚ͩ៉ྷʹ

  58. 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)
  59. 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Ͱ౰͍ͯͯ͘ ద౰ͳΫϥε໊ߟ͑ΔΑΓ؆୯
  60. 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', } } ΦϒδΣΫτͰهड़
  61. ΍ͬͯΈΔ • :hoverͰϘλϯͷ৭Λม͑Δ • textareaͷߴ͞Λม͑Δ

  62. 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(), } } }
  63. 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(), } } }
  64. 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> ) } }
  65. 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> ) } }
  66. ·ͱΊ • ΫϥΠΞϯταΠυͷॻ͖ํ͕େ͖͘มΘΔ • Ϗϡʔ΋CSS΋jsʹೖΕΔ • ίϯϙʔωϯτͷڍಈ͸͚ͦͩ͜ΈΕ͹શ ͯ෼͔Δʂ • αʔόʔαΠυϨϯμϦϯά؆୯