Help us understand the problem. What is going on with this article?

カウントアプリを作ってReduxの流れを理解する。

More than 1 year has passed since last update.

カウントアプリの概要

スクリーンショット 2019-06-17 12.00.51.png
増えたり減ったりします。

Reactのみでこのアプリを実装。

Reactのみでカウントアプリを作成して、後からReduxを導入します。
create-react-appでReactアプリを作成してある程で話が進みます

# ディレクトリ構造

react-app/
        ├  node_modules
        ├  public
        ├  src
        │   ├ components
        │   │      ├ App.js
        │   │
        │   ├ index.js
        ├  package.json
        ├  README.md
        ├  yarn.lock

表示部分の実装

src/components/App.js
import React , { Component } from 'react'; // Componentをimportする
class App extends Component{
  render(){
    return(
      <React.Fragment>
        <div>count : 0</div>
        <button>+1</button>
        <button>-1</button>
      </React.Fragment>
    )
  }
}

export default App;

class componentを使って実装していくので、importしておく。

App.jsをcomponents配下に移動させたので、src/index.jsも変更する。

src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App'; //ここを変更

ReactDOM.render(<App />, document.getElementById('root'));

これで、動かないが画面への表示される。

次にボタンを押すと、stateが更新され、countの数字が変わるようにしていく。

src/components/App.js
import React , { Component } from 'react';

class App extends Component{

  constructor(props){
    super(props)
    this.state = { count: 0 } //countの初期値を設定
  }

  // プラスボタンが押された時の処理
  handlePlusButton = () => {
    this.setState({ count: this.state.count + 1})
  }

  // マイナスボタンが押された時の処理
  handleMinusButton = () => {
    this.setState({ count: this.state.count - 1})
  }

  render(){
    return(
      <React.Fragment>
        <div>count : { this.state.count }</div>
        <button onClick={this.handlePlusButton}>+1</button> 
        <button onClick={this.handleMinusButton}>-1</button>
      </React.Fragment>
    )
  }
}

export default App;

Reactのみのカウントアプリはこれで完成。

Reduxの導入

Reduxを利用するためにパッケージをinstallする。

yarn add react react-redux

ディレクトリ構造を変更する。

react-app/
        ├  node_modules
        ├  public
        ├  src
        │   ├ components
        │   │      ├ App.js
        │   │
        │   ├ actions 
        │   │      ├ index.js 
        │   │
        │   ├ reducers
        │   │      ├ index.js
        │   │      ├ count.js
        │   │
        │   ├ utils
        │   │      ├ index.js
        │   │
        │   ├ index.js
        ├  package.json
        ├  README.md
        ├  yarn.lock

アクション(actions)の作成

アクションには、アプリケーション内部で起こる出来事を記載する。jsのオブジェクトで処理は書かない。
オブジェクト内部には、ユニークな名前を持つ'type'キーと、typeに対応する値を持つ。

src/actions/index.js
export const INCREMENT = 'INCREMENT'  //再利用することが多いので定義。あとでutils/indexに移動します。
export const DECREMENT = 'DECREMENT'  //再利用することが多いので定義。あとでutils/indexに移動します。

//プラスする処理の名前
export const increment = () =>({
    type: INCREMENT
  })

//マイナスする処理の名前
export const decrement = () =>({
    type: DECREMENT
  })

ここで、utils/index.jsを作成して、上記のINCREMENTとDECREMENTを定義して、src/actions/index.jsからimportする仕組みにする。

src/utils/index.js
export const INCREMENT = 'INCREMENT'  
export const DECREMENT = 'DECREMENT'  
src/actions/index.js
import { INCREMENT, DECREMENT } from './utils' //ここを追加

//プラスする処理の名前
export const increment = () =>({
    type: INCREMENT
  })

//マイナスする処理の名前
export const decrement = () =>({
    type: DECREMENT
  })

reducerの作成

actionが発生した時に、そのactionのtypeに応じて、stateをどう変化させるのか定義する。

src/reducers/index.js
// 全てのreducerをまとめるためのファイル
import { combineReducers } from 'redux' //applicationの全reducerをまとめるために必要
import count from './count'

export default combineReducers({ count })
// export default combineReducers({ count, hoge, huga }) のように書く。これで、count,hoge,hugaが使えるようになる。
src/reducers/count.js
import { INCREMENT, DECREMENT } from '../utils'

const initialState = { value: 0 } //状態の初期値を定義。

const count = (state = initialState, action) => {
  switch(action.type){
    case INCREMENT:
      return {value: state.value + 1} //変更したい状態を返す。※ここではまだ状態は変わっていない。
    case DECREMENT:
      return {value: state.value - 1}
    default:
      return state
  }
}

export default count;

Storeの作成

アプリケーションの状態(state)を保持する。

src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux' //storeを作成するために必要
import { Provider } from 'react-redux' //作成したstoreを全componentに渡すために必要

import App from './components/App';
import reducer from './reducers' //作成したreducerをimportしておく

const store = createStore(reducer) //storeの作成

ReactDOM.render(
  <Provider store={store}> 
    <App />
  </Provider>, document.getElementById('root'));

これで、redux部分はほとんど完成。

ReactとReduxのつなぎこみ

src/components/App.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { increment, decrement } from '../actions' //actionsのimport


class App extends Component{
  render(){
    const props = this.props //状態やアクションを渡す。mapStateToPropsで定義されている。関数はmapDipatchTopropsで渡されている。
    return(
        <React.Fragment>
        <div>count : { props.value }</div>
        <button onClick={props.increment}>+1</button>
        <button onClick={props.decrement}>-1</button>
        </React.Fragment>

    )
  }
}

// stateの情報からこのcomponentで必要なものを取り出してcomponent内のpropsとしてマッピング機能
// 引数には、状態のトップレベルを示すstateを書いて、どういったオブジェクトをpropsとして対応させるのか、関数の戻り値として定義。
const mapStateToProps = (state) =>{
  return {value: state.count.value}
}

//あるアクションが発生した時に、reducerにタイプに応じた状態遷移を実行させるための関数がdipatche。
//このdispatch関数を引数に置くことで、このcomponentで必要になるdipatche関数を宣言する。
const mapDispatchToProps = (dispatch) =>(
  {
    increment: () => dispatch(increment()),
    decrement: () => dispatch(decrement())
  }
)


export default connect(mapStateToProps, mapDispatchToProps)(App);

ここでつまづいたのが、

const props = this.props

これは、mapStateToPropsとmapDispatchToPropsでそれぞれから状態を受け取っていた。
なので、今回だと、valueとincrement関数、decrement関数が渡されている。

まとめ

ソースコード
https://github.com/satokiyoshino/react-count-app
compileエラーが起きないで、バグが起きるとデバッグがめちゃくちゃ難しい。
Chromeの拡張で、Redux Devtoolがあるので、これを使えば少し助かるかもしれない。
https://github.com/zalmoxisus/redux-devtools-extension

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした