Reduxのサンプルのショッピングカートをステップ・バイ・ステップで Part.2

[Reduxのサンプルのショッピングカートをステップ・バイ・ステップで Part.1](https://qiita.com/tkarasuma/items/6aa4fa5162d0a6452cd5)の続き


#1. storeをつくる
productsステートなどを保持するstoreをつくる。

###商品データを管理するAPIをつくる。

商品のjsonデータを読み込んだり、決裁したりするためのshopオブジェクトをつくる。
サーバーとのやり取りを想定するため、setTimeoutでそれっぽくしているようだ。

```javascript:api/shop.js
import _products from './products.json'

const TIMEOUT = 100;

export default {
    getProducts: (cb, timeout) => setTimeout( 
        ()=>cb(_products),timeout || TIMEOUT),
    buyProducts: (_payload, cb, timeout) => setTimeout(
        ()=>cb(_products), timeout || TIMEOUT)
}
```

###アクションのtypeを定義

```javascript:constants/ActionTypes.js
export const RECEIVE_PRODUCTS = 'RECEIVE_PRODUCTS'
```
###対応するアクションを実装

```javascript:actions/index.js
import shop from '../api/shop'
import * as types from '../constants/ActionTypes'

//引数一個を受ける関数を返す関数。　この引数には実行時、dispatchが割り当てられる。
export const getAllProducts = () => d => {
    // コールバックが引数。実行時、商品データの配列が入る。api/products.json
    shop.getProducts(
        products => d(
            {
                type: types.RECEIVE_PRODUCTS,
                products
            }
        )
    )
}
```
###productsリデューサをつくる

```javascript:reducers/products.js
import { combineReducers } from 'redux';
import { RECEIVE_PRODUCTS } from '../constants/ActionTypes'

const byId = (state = {}, action) => {
    switch (action.type) {
        case RECEIVE_PRODUCTS:
            return {
                ...state,
                // 配列をidをプロパティにするオブジェクトに加工
                //スプレッド演算子で要素を展開
                ...action.products.reduce(
                    (obj, product) => {
                        obj[product.id] = product;
                        return obj;
                    }, {}
                )
            }
        default:
            return state;
    }
}

const visibleIds = (state=[],action)=>{
    switch (action.type) {
        case RECEIVE_PRODUCTS:
            return action.products.map(product => product.id);
        default:
            return state
    }
}

export default combineReducers({byId,visibleIds})

```
###リデューサを一本化する準備

のちにcartリデューサもつくるので、そのための準備

```javascript:reducers/index.js
import { combineReducers }  from 'redux';
import products  from './products'

export default combineReducers({products});
```

###エントリーポイントのindex.jsを修正

```javascript:index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import reducer from './reducers'
import { getAllProducts } from './actions'

const store = createStore(
    reducer,
    applyMiddleware(thunk)
)
store.dispatch(getAllProducts())
store.subscribe(
    () =>console.log(store.getState())
)

ReactDOM.render(
    <Provider store={store}>
    <h1>ダミーH1</h1>
    </Provider>,
    document.getElementById('root')
)
```
###実行結果

![2018_0612_1617_55.jpg](https://qiita-image-store.s3.amazonaws.com/0/76757/d4b29605-5cc0-cab4-a5e8-1817e683a5d4.jpeg)

[ソースコード 02-1](https://github.com/tkarasuma/JavaScript/tree/master/Redux_shopping_cart_02-1)
  
#2. コンポーネントを作っていく

###productsリデューサに関数追加

商品オブジェクトの配列を返してくれる関数をつくる。
getVisibleProducts関数　と　　getProduct関数を追加

```javascript:reducers\products.js
import { combineReducers } from 'redux';
import { RECEIVE_PRODUCTS } from '../constants/ActionTypes'

const byId = (state = {}, action) => {
    switch (action.type) {
        case RECEIVE_PRODUCTS:
            return {
                ...state,
                // 配列をidをプロパティにするオブジェクトに加工
                //スプレッド演算子で要素を展開
                ...action.products.reduce(
                    (obj, product) => {
                        obj[product.id] = product;
                        return obj;
                    }, {}
                )
            }
        default:
            return state;
    }
}

const visibleIds = (state=[],action)=>{
    switch (action.type) {
        case RECEIVE_PRODUCTS:
            return action.products.map(product => product.id);
        default:
            return state
    }
}

export default combineReducers({byId,visibleIds})

//idでstateから特定の商品オブジェクトを取得
export const getProduct = (state,id)=>{
    return state.byId[id]
}
//商品idの配列から、商品オブジェクトの配列取得
export const getVisibleProducts = state =>{
    return state.visibleIds.map(id=> getProduct(state,id) )
}
```

###stateを見れる関数を追加

あとで、ボタンをクリックするとstateを逐次覗けるように自作のdisplayState関数を追加。

```javascript:actions\index.js
import shop from '../api/shop'
import * as types from '../constants/ActionTypes'

//引数1個を受ける関数を返す関数。　この引数には実行時、dispatchが割り当てられる。
export const getAllProducts = () => d => {
    // コールバックが引数。実行時、商品データの配列が入る。api/products.json
    shop.getProducts(
        products => d(
            {
                type: types.RECEIVE_PRODUCTS,
                products
            }
        )
    )
}
//引数2個を受ける関数を返す関数。
// この引数には実行時、dとgにはdispatchとgetStateが割り当てられる。
export const displayState = () => (d, g) => {
    console.log(g());
  }
```

###Productコンポーネント追加

個々の商品情報を表示する部分

```jsx:components\Product.js
import React from 'react'
import PropTypes from 'prop-types'

//個々の商品情報の表示部分
const Product = ({ price, quantity, title }) => (
    <div className="clearfix mb-1">
        <span className="p-1  bg-success text-white rounded m1 float-left">{title}</span><span className="w-50 float-right"><span className="float-right w-25 text-right">{quantity ? `  ${quantity}個` : `0個`}</span><span className="float-right w-25 mr-3">&#165;{price}</span></span>
    </div>
)

Product.propTypes = {
    price: PropTypes.number,
    quantity: PropTypes.number,
    title: PropTypes.string
  }
  
  export default Product
```

###ProductItemコンポーネント追加

個々の商品データを表示、管理する。Productコンポーネントを内包。

```jsx:components\ProductItem.js
import React from 'react'
import PropTypes from 'prop-types'
import Product from './Product'

//個々の商品データを入れる入れ物
// カートに入れるためのボタンもつける
const ProductItem = ({product})=>(
    <div className="clearfix p-1 rounded mx-auto" style={{ marginBottom: 5, width: '30em', border:'5px solid #eee'}} >
    <Product
      title={product.title}
      price={product.price}
      quantity={product.inventory} />
    <hr />
    <button className="btn btn-warning float-right"
      disabled={product.inventory > 0 ? '' : 'disabled'}>
      {product.inventory > 0 ? 'カートに入れる' : '売り切れです'}
    </button>
  </div>   
)


ProductItem.propTypes = {
    product: PropTypes.shape({
      title: PropTypes.string.isRequired,
      price: PropTypes.number.isRequired,
      inventory: PropTypes.number.isRequired
    }).isRequired
  }
  
  export default ProductItem
```
###ProductsListコンポーネント追加

商品リストをいれる入れ物。好きなときにstateを覗けるボタン付き。

```jsx:components\ProductsList.js
import React from 'react'
import PropTypes from 'prop-types'


const ProductsList = ({ title, children,onClick_displayState }) => (
  <div  className="mx-auto">
    <h5 className="text-center">{title}</h5>
    <div>{children}</div>
    <button className="btn btn-primary mx-auto d-block mb-1" style={{width:"6em"}} onClick={onClick_displayState}>state 表示</button>
 </div>
)

ProductsList.propTypes = {
  children: PropTypes.node,
  title: PropTypes.string.isRequired,
  onClick_displayState:PropTypes.func.isRequired
}

export default ProductsList

```

###ProductsContainerコンテナ追加

stateの情報とdispatchを配下のコンポーネントにわたす起点

```jsx:containers\ProductsContainer.js

import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { displayState } from '../actions'
import { getVisibleProducts } from '../reducers/products'
import ProductItem from '../components/ProductItem'
import ProductsList from '../components/ProductsList'


// productsは商品データのオブジェクトの配列。　
// reducers/product//getVisibleProducts関数で取得
// displayStateは　stateの中身を覗ける関数。ボタンで使う。

const ProductsContainer = ({ products, displayState }) => (
    <ProductsList title="商品一覧" onClick_displayState={() => displayState()}>
        {products.map(product =>
            <ProductItem
                key={product.id}
                product={product}
            />
        )}
    </ProductsList>
)

ProductsContainer.propTypes = {
    products: PropTypes.arrayOf(PropTypes.shape({
        id: PropTypes.number.isRequired,
        title: PropTypes.string.isRequired,
        price: PropTypes.number.isRequired,
        inventory: PropTypes.number.isRequired
    })).isRequired,
    displayState: PropTypes.func.isRequired
}

// ここで ステートの商品情報のオブジェクトの配列を
// productsプロップスに結びつける。
const mapStateToProps = state => {
    return {
        products: getVisibleProducts(state.products)
    }
}

export default connect(
    mapStateToProps,{displayState}
)(ProductsContainer)

```

###Appコンテナ追加

あとで、ここにCartコンテナを追加する。

```jsx:containers\App.js
import React from 'react'
import ProductsContainer from './ProductsContainer'

const App = () => (
  <div className="mx-auto" style={{width:"32em"}}>
    <h4 className="text-center alert alert-danger">🍓フルーツ市場🍈</h4>
    <ProductsContainer />
  </div>
)

export default App

```

###エントリーポイント修正

```jsx:index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import reducer from './reducers'
import { getAllProducts } from './actions'
import App from './containers/App'

const store = createStore(
    reducer,
    applyMiddleware(thunk)
)
store.dispatch(getAllProducts())
// store.subscribe(
//     () => console.log(store.getState())
// )

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

```
###実行結果

![2018_0613_1217_20.jpg](https://qiita-image-store.s3.amazonaws.com/0/76757/37a8b878-c916-ab9d-bea2-4ed81c9f58fb.jpeg)

この時点では "カートに入れる"は機能しない。"state 表示" でコンポーネントのpropsに情報が伝わっているか確認できる。

[ソースコード　02-2](https://github.com/tkarasuma/JavaScript/tree/master/Redux_shopping_cart_02-2)