あいつの日誌β

働きながら旅しています。

React tutorial (3)

あらすじ

前回は React の基本的な作法に触れました。 今回は Redux を使ってみます。

目次

  • 開発環境を準備
  • React の基本的な Life Cycle に触れる
  • redux に触れる <= 今日やること
  • redux-saga に触れる
  • react router に触れる
  • npm で公開されている components を導入して echo system を体感する
  • redux-form に触れる
  • react-select に触れる

今日やること

f:id:okamuuu:20170122153154g:plain

準備

npm install --save redux redux-actions redux-logger react-redux
mkdir src/containers src/store src/reducers
touch src/containers/Posts.js src/actions.js src/store/configureStore.js src/reducers/index.js

各種ファイル作成

create src/actions.js

import { createAction } from "redux-actions"

export const SET_POSTS = 'SET_POSTS'

export const setPosts = createAction(SET_POSTS)

create src/reducers/index.js

import { combineReducers } from 'redux'
import { SET_POSTS } from '../actions'

const initialState = { 
  posts: []
}

function app(state = initialState, action) {
  switch (action.type) {
  case SET_POSTS:
    return Object.assign({}, state, {posts: action.payload})
  default:
    return state
  }
}

const rootReducer = combineReducers({
  app,
})

export default rootReducer

create src/store/configureStore.js

import { createStore, applyMiddleware } from 'redux'
import createLogger from 'redux-logger'
import rootReducers from '../reducers'

const logger = createLogger()
const middlewares = [logger]
const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore)

export default function configureStore(initialState) {
  const store = createStoreWithMiddleware(rootReducers, initialState)
  if (module.hot) {
    module.hot.accept('../', () => {
      const nextRootReducer = require('../reducers').default
      store.replaceReducer(nextRootReducer)
    })  
  }
  return store
}

create src/containers/Posts.js

import React, {Component} from 'react'
import { connect } from 'react-redux'
import { setPosts } from '../actions'
import { PostItem } from '../components/Posts'
import { posts } from '../fixtures'

class Posts extends Component {

  handleButtonClick() {
    this.props.dispatch(setPosts(posts))
  }

  render() {
    const {posts} = this.props.app

    return (
      <div className="container">
        <h2>Posts</h2>
        {posts.map((post) => (
          <PostItem key={post.id} post={post} />
        ))}
        <button className="btn btn-default" onClick={this.handleButtonClick.bind(this)}>Push</button>
      </div>
    )
  }
}

const select = state => (state)

export default connect(select)(Posts)

edit src/Root.js

import 'babel-polyfill'
import 'bootstrap'
import 'bootstrap/dist/css/bootstrap.css'
import React from 'react'
import { Provider } from 'react-redux'

import configureStore from './store/configureStore'
import Posts from './containers/Posts'

const store = configureStore()

export default (props) => (
  <Provider store={store}>
    <Posts />
  </Provider>
)

Implement it

% node devServer.js

edit src/components/Posts.js to remove wasted codes

import React, {Component} from 'react'

// stateless component
export const PostItem = (props) => (
  <div>
    <h3>{props.post.title}</h3>
    <p>{props.post.body}</p>
  </div>
)

containers と conmponents について

src/containerssrc/components の違いについて明確な答えを私はよく知らないのですが、とりあえず connect を使っているコンポーネントは containers に配置する。でいいと思います。 で、connect していないものは containers に入れてはいけないのか?というとそうではないので、とりあずよく分からない場合は全部 containers に入れておいていいと思います。

私は共通化できる stateless な コンポーネントを見つけたときだけ src/components に移動させるようにしています。

それから最初はconnect が何やっているかわかりづらいと思います。これは親、子、孫 のコンポーネントがあった場合、親から孫までデータ渡して孫から親までイベントを伝播させるのではなく、直接孫にデータを渡して、イベントを引き取るために使っている。というイメージで覚えておいてとりあえず次に進んでしまってください。

まとめ

今回は redux について触れました 次回は redux-thunk をすっとばして redux-saga について触れます。