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

React速習会@Wantedly

 React速習会@Wantedly

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

Kento Moriwaki

February 04, 2016
Tweet

More Decks by Kento Moriwaki

Other Decks in Technology

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

  4. ४උ
    git clone [email protected]:KentoMoriwaki/react_sokushu.git
    cd react_sokushu
    npm install
    npm install -g webpack
    • Node͸ݹ͗͢Δͱಈ͔ͳ͍͔΋
    • v5.3.0Ͱ֬ೝ

    View Slide

  5. ४උ
    • Chrome dev toolͰReactͷσόοά͕ग़དྷΔ
    https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi

    View Slide

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

    View Slide

  7. ͱΓ͋͑ͣಈ͔͢
    git checkout component
    npm start
    webpack —watch
    • http://localhost:3000ʹΞΫηε
    • ೖྗͰ͖ͳ͍ϑΥʔϜ͕දࣔ͞ΕΔ

    View Slide

  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 (


    Create Issue


    Save


    )
    }
    }

    View Slide

  9. Component
    • Reactͷओ໾
    • View + Controller
    • View͸JSXͰɺrenderϝιουʹهड़͢Δ
    • this.stateʹঢ়ଶΛอ࣋͢Δ

    View Slide

  10. One-way data binding
    • ೖྗͯ͠΋σʔλ͸มΘΒͳ͍
    • ࣗ෼Ͱ໌ࣔతʹมߋ͠ͳ͍ͱɺϏϡʔ͸ม
    ΘΒͳ͍
    • σʔλ͕มΘΔͱϏϡʔ͕ࣗಈతʹมΘΔ
    • this.setState(newState)Ͱมߋ

    View Slide

  11. One-way data binding
    • onChange={} ʹϋϯυϥΛॻ͍ͯঢ়ଶΛมߋ͢Δ
    onChangeTitle(e) {
    this.setState({ title: e.target.value })
    }
    onChangeDescription(e) {
    this.setState({ description: e.target.value })
    }
    render() {
    return (


    Create Issue


    Save


    )
    }

    View Slide

  12. One-way data binding
    • σʔλ͕Ұํ޲ʹ͔͠ྲྀΕͳ͍
    • ߟ͑Δ͜ͱ͕γϯϓϧʹͳΔ
    • σʔλ͔ΒͲ͏΍ͬͯϏϡʔΛ࡞Δ͔
    • σʔλΛͲ͏มߋ͢Δ͔

    View Slide

  13. ෳ਺ίϯϙʔωϯτ
    • ೖྗͨ͠σʔλΛϦετͰද͍ࣔͨ͠
    • git checkout list
    • ͖ͬ͞ͷAppΛIssueFormʹ
    • ৽͘͠IssueListίϯϙʔωϯτΛ࡞੒

    View Slide

  14. ෳ਺ίϯϙʔωϯτ

    View Slide

  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 (




    )
    }
    }

    View Slide

  16. Property
    • ίϯϙʔωϯτ͸ϓϩύςΟΛ࣋ͭ
    • JSXͷଐੑͱͯ͠ड͚औΕΔ

    • ܕͷఆٛͳͲ͕ߦ͑Δ
    • PropTypesͱݺ͹ΕɺͲ͏͍͏ଐੑ໊ͰͲ͏͍͏ܕ
    ͷσʔλΛड͚औΔ͔ఆٛͰ͖Δ

    View Slide

  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 (




    )
    }
    }

    View Slide

  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 (




    )
    }
    }

    View Slide

  19. components/IssueList.js
    • this.props Ͱ౉͞Εͨଐੑ஋ΛࢀরͰ͖Δ
    export default class IssueList extends Component {
    render() {
    return (

    { this.props.issues.map((issue) => {
    return (

    {issue.id}
    {issue.title}
    {issue.description}

    )
    }) }

    )
    }
    }

    View Slide

  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 (


    Create Issue


    Save


    )
    }
    }

    View Slide

  21. StateͱProperty
    • State͸Mutable
    • Property͸Immutable
    • StateΛࢠComponentͷPropertyͱͯ͠౉͢
    • ࢠComponent͔ΒState͕มߋ͞ΕΔ͜ͱ͸ͳ͍
    • มߋ͸ίʔϧόοΫͰ

    View Slide

  22. Flux

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  26. ΢ΣϒαΠτͱࣅͯΔ
    • యܕతͳ΢ΣϒαΠτ
    • αʔόʔ͕ϦΫΤετΛड͚औΓɺ DBͷσʔλΛߋ৽͢Δ
    • ৽͍͠σʔλΛݩʹϖʔδΛҰ͔ΒϨϯμϦϯά͢Δ
    • Flux
    • Dispatcher͕ActionΛड͚औΓɺStoreΛߋ৽͢Δ
    • ৽͍͠StoreΛݩʹϖʔδΛҰ͔ΒϨϯμϦϯά͢Δ
    • ຊ౰͸React্͕ख͍͜ͱඞཁͳ෦෼͚ͩϨϯμϦϯάͯ͘͠ΕΔ

    View Slide

  27. Flux࣮૷
    • ΞʔΩςΫνϟͳͷͰ࣮૷͸͍Ζ͍Ζ
    • MVCϑϨʔϜϫʔΫ͕͍ͬͺ͍ଘࡏ͢ΔΈ
    ͍ͨʹ
    • ࠷ۙਓؾͷReduxΛ৮ͬͯΈΔ

    View Slide

  28. Redux
    • Flux࣮૷ͷ̍ͭ
    • ಛ௃
    • ΞϓϦέʔγϣϯͷঢ়ଶΛ̍ͭͰѻ͏
    • ঢ়ଶ͸read-only
    • มߋ͸७ਮͳؔ਺Ͱॻ͚Δ
    • git checkout flux

    View Slide

  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 }
    }

    View Slide

  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
    }

    View Slide

  31. Container
    • ಛผͳComponent
    • ReduxͱReactΛܨ͛Δଘࡏ
    • ҎԼͷ΋ͷΛpropertyͱͯ͠ड͚औΕΔΑ͏ʹͳΔ
    • ReducerʹΑͬͯ࡞ΒΕΔঢ়ଶ(store)
    • ActionΛൃߦ͢ΔͨΊͷdispatchؔ਺

    View Slide

  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 (



    )
    }
    }
    function mapStateToProps(state) {
    return {
    issues: state.issues
    }
    }
    export default connect(mapStateToProps)(App)

    View Slide

  33. Container
    issuesͱdispatch͕౉͞Ε͍ͯΔ

    View Slide

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

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  37. reducers/issues.js
    • ৽͍͠഑ྻΛฦ͢
    • stateʹ୅ೖͯ͠͸͍͚ͳ͍
    case ADD:
    let issue = action.issue
    return [
    ...state,
    {
    id: state.length + 1,
    title: issue.title,
    description: issue.description
    }
    ]

    View Slide

  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 (



    )
    }
    }

    View Slide

  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 (



    )
    }
    }

    View Slide

  40. Ajax
    • αʔόʔ͔ΒσʔλΛऔಘ͍ͨ͠
    • ActionͰ΍Δͷ͕Ұൠత
    • redux-thunk ͱ͍͏middlewareΛ࢖͏
    • git checkout ajax

    View Slide

  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))
    })
    }
    }

    View Slide

  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))
    })
    }
    }

    View Slide

  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
    }

    View Slide

  44. αʔόʔͰϨϯμϦϯάͯ͠
    ΈΔ

    View Slide

  45. αʔόʔαΠυϨϯμϦϯά
    • SEOͷ؍఺
    • Ϋϩʔϥʔ͕Ͳ͜·ͰJavaScriptΛ্ख͘ѻ͍͑ͯΔ͔ෆ҆
    • ੩తͳϖʔδΛฦ͍ͨ͠
    • Ϣʔβʔମݧ
    • ϖʔδʹΞΫηε͔ͯ͠ΒɺReactͷ࣮ߦΛ଴ͬͯɺAPIϦ
    ΫΤετૹͬͯɺඳը͞ΕΔͷ͕ɺ஗͍

    View Slide

  46. ࢓૊Έ
    • DOM৮ͬͯͳ͍ͷͰɺαʔόʔଆͰ΋࣮ߦͰ͖ΔJS
    ʹͳ͍ͬͯΔ
    • ΞϓϦέʔγϣϯͷঢ়ଶ͕ܾ·Ε͹ɺϏϡʔ͸ܾ·
    Δ
    • DOMͱͯ͠Ͱ͸ͳ͘ɺจࣈྻͱͯ͠Ϩϯμʔ͢Δ
    • ͦͷঢ়ଶͷΦϒδΣΫτΛಉ࣌ʹฦ͢

    View Slide

  47. ΍ͬͯΈΔ
    • git checkout ssr
    • npm start ͷ࠶ىಈ

    View Slide

  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(



    )
    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)

    View Slide

  49. server.js
    • renderFullPageͰrenderToString͞ΕͨHTML
    ͱɺॳظstateΛςϯϓϨʔτʹૠೖ͢Δ͚ͩ
    //TODO: Interpolate html and initialState
    function renderFullPage(html, initialState) {
    return `



    React Sokushu






    `
    }

    View Slide

  50. server.js
    • renderFullPageͰrenderToString͞ΕͨHTML
    ͱɺॳظstateΛςϯϓϨʔτʹૠೖ͢Δ͚ͩ
    function renderFullPage(html, initialState) {
    return `



    React Sokushu


    ${html}
    <br/>window.__INITIAL_STATE__ = ${JSON.stringify(initialState)}<br/>



    `
    }

    View Slide

  51. view-source

    View Slide

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


    ,
    document.getElementById("root")
    )

    View Slide

  53. CSS in React

    View Slide

  54. CSS in React
    • CSSͷ໰୊఺
    • ໊લͷিಥ
    • Ϋϥε໊Λ͍ͬͺ͍ߟ͑ͳ͍ͱ
    • ࢖ΘΕͳ͘ͳͬͨίʔυ͕ফͤͳ͍
    • ංେԽ͢Δcss
    • JSXͰϏϡʔΛ.jsͰॻ͘Α͏ʹͳͬͨͷͰɺελΠϧ΋Ұॹʹॻ͖
    ͍ͨ

    View Slide

  55. CSS in React
    • ΫϥεΛ෇͚Δ୅ΘΓʹinlineͰॻ͘
    • style={this.styles.base} Έ͍ͨʹ
    • ܧঝͱ͔ਏ͍
    • :hoverͱ͔͔͚ͳ͍

    View Slide

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

    View Slide

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

    View Slide

  58. components/IssueForm.js
    import React, { Component } from 'react'
    import Radium from 'radium'
    import color from 'color'
    class IssueForm extends Component {
    render() {
    return (



    Create Issue


    Save



    )
    }
    }
    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)

    View Slide

  59. components/IssueForm.js
    class IssueForm extends Component {
    render() {
    return (



    Create Issue


    Save



    )
    }
    }
    inlineͰ౰͍ͯͯ͘
    ద౰ͳΫϥε໊ߟ͑ΔΑΓ؆୯

    View Slide

  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',
    }
    }
    ΦϒδΣΫτͰهड़

    View Slide

  61. ΍ͬͯΈΔ
    • :hoverͰϘλϯͷ৭Λม͑Δ
    • textareaͷߴ͞Λม͑Δ

    View Slide

  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(),
    }
    }
    }

    View Slide

  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(),
    }
    }
    }

    View Slide

  64. components/IssueForm.js
    • ഑ྻͰࢦఆ͢Δͱɺoverrideͯ͘͠ΕΔ
    • style={[styles.input, styles.textarea]}
    class IssueForm extends Component {
    render() {
    return (



    Create Issue


    Save



    )
    }
    }

    View Slide

  65. components/IssueForm.js
    • ഑ྻͰࢦఆ͢Δͱɺoverrideͯ͘͠ΕΔ
    • style={[styles.input, styles.textarea]}
    class IssueForm extends Component {
    render() {
    return (



    Create Issue


    Save



    )
    }
    }

    View Slide

  66. ·ͱΊ
    • ΫϥΠΞϯταΠυͷॻ͖ํ͕େ͖͘มΘΔ
    • Ϗϡʔ΋CSS΋jsʹೖΕΔ
    • ίϯϙʔωϯτͷڍಈ͸͚ͦͩ͜ΈΕ͹શ
    ͯ෼͔Δʂ
    • αʔόʔαΠυϨϯμϦϯά؆୯

    View Slide