用redux构建购物车

很久没更新博客了,最近要用到react,再来跟大家分享一个redux案例吧。

1 [
2   {"id": 1, "title": "iPad 4 Mini", "price": 500.01, "inventory": 2},
3   {"id": 2, "title": "H&M T-Shirt White", "price": 10.99, "inventory": 10},
4   {"id": 3, "title": "Charli XCX - Sucker CD", "price": 19.99, "inventory": 5}
5 ]

这是购物车里面的数据

1 import _products from './products.json' //把json引进来
2 
3 const TIMEOUT = 100
4 
5 export default {
6   getProducts: (cb, timeout) => setTimeout(() => cb(_products), timeout || TIMEOUT), //初始化产品
7   buyProducts: (payload, cb, timeout) => setTimeout(() => cb(), timeout || TIMEOUT)
8 }
9 //1:得到产品明细 (延迟)   2:购买产品

下面是购物车的操作

 1 import shop from '../api/shop'
 2 import * as types from '../constants/ActionTypes'
 3 
 4 const receiveProducts = products => ({
 5   type: types.RECEIVE_PRODUCTS,
 6   products: products
 7 })
 8 
 9 export const getAllProducts = () => dispatch => {
10   shop.getProducts(products => {
11     dispatch(receiveProducts(products))
12   })
13 }  //得到所有产品  从json里里面
14 
15 const addToCartUnsafe = productId => ({
16   type: types.ADD_TO_CART,
17   productId  //得到dispatch发送的数据
18 })
19 
20 export const addToCart = productId => (dispatch, getState) => {
21   if (getState().products.byId[productId].inventory > 0) {
22     dispatch(addToCartUnsafe(productId))
23   }
24 } //增加产品  只有库存大于0时
25 
26 export const checkout = products => (dispatch, getState) => {
27   const { cart } = getState()
28 
29   dispatch({
30     type: types.CHECKOUT_REQUEST
31   })
32   shop.buyProducts(products, () => {
33     dispatch({
34       type: types.CHECKOUT_SUCCESS,
35       cart
36     })  //当有买入或卖出时都会checkout
37     // Replace the line above with line below to rollback on failure:
38     // dispatch({ type: types.CHECKOUT_FAILURE, cart })
39   })
40 }

需要知道actiontypes,下面是actiontype时文件

1 export const ADD_TO_CART = 'ADD_TO_CART'
2 export const CHECKOUT_REQUEST = 'CHECKOUT_REQUEST'
3 export const CHECKOUT_SUCCESS = 'CHECKOUT_SUCCESS'
4 export const CHECKOUT_FAILURE = 'CHECKOUT_FAILURE'
5 export const RECEIVE_PRODUCTS = 'RECEIVE_PRODUCTS'

下面展示产品的逻辑

 1 import { combineReducers } from 'redux'
 2 import { RECEIVE_PRODUCTS, ADD_TO_CART } from '../constants/ActionTypes'
 3 
 4 const products = (state, action) => {
 5   switch (action.type) {
 6     case ADD_TO_CART:
 7       return {
 8         ...state,
 9         inventory: state.inventory - 1
10       }
11     default:
12       return state
13   }
14 }  //往购物车里面添加一个  库存就会减少1个 注意es6语法
15 
16 const byId = (state = {}, action) => {
17   switch (action.type) {
18     case RECEIVE_PRODUCTS:
19       return {
20         ...state,
21         ...action.products.reduce((obj, product) => {
22           obj[product.id] = product
23           return obj
24         }, {})
25       } // 重新初始化购物车  
26     default:
27       const { productId } = action
28       if (productId) {
29         return {
30           ...state,
31           [productId]: products(state[productId], action)
32         }
33       }
34       return state
35   }
36 }
37 
38 const visibleIds = (state = [], action) => {
39   switch (action.type) {
40     case RECEIVE_PRODUCTS:
41       return action.products.map(product => product.id)
42     default:
43       return state
44   }
45 }
46 
47 export default combineReducers({
48   byId,
49   visibleIds
50 })  //合并reducer
51 
52 export const getProduct = (state, id) =>
53   state.byId[id]
54 
55 export const getVisibleProducts = state =>
56   state.visibleIds.map(id => getProduct(state, id)) //得到visible的产品

下面再就是购物车的

 1 import {
 2   ADD_TO_CART,
 3   CHECKOUT_REQUEST,
 4   CHECKOUT_FAILURE
 5 } from '../constants/ActionTypes'
 6 
 7 const initialState = {
 8   addedIds: [],
 9   quantityById: {}
10 }
11 
12 const addedIds = (state = initialState.addedIds, action) => {
13   switch (action.type) {
14     case ADD_TO_CART:
15       if (state.indexOf(action.productId) !== -1) {
16         return state
17       }
18       return [ ...state, action.productId ]
19     default:
20       return state
21   }
22 } //如果已添加的id没有这个  就可以添加
23 
24 const quantityById = (state = initialState.quantityById, action) => {
25   switch (action.type) {
26     case ADD_TO_CART:
27       const { productId } = action
28       return { ...state,
29         [productId]: (state[productId] || 0) + 1
30       } // 往购物车添加商品 没有就是0
31     default:
32       return state
33   }
34 }
35 
36 export const getQuantity = (state, productId) =>
37   state.quantityById[productId] || 0
38 
39 export const getAddedIds = state => state.addedIds 
40 
41 const cart = (state = initialState, action) => {
42   switch (action.type) {
43     case CHECKOUT_REQUEST:
44       return initialState
45     case CHECKOUT_FAILURE:
46       return action.cart
47     default:
48       return {
49         addedIds: addedIds(state.addedIds, action),
50         quantityById: quantityById(state.quantityById, action)
51       }
52   }
53 }   
54 
55 export default cart

 index.js   

 1 import { combineReducers } from 'redux'
 2 import cart, * as fromCart from './cart'
 3 import products, * as fromProducts from './products'
 4 
 5 export default combineReducers({
 6   cart,
 7   products
 8 })
 9 
10 const getAddedIds = state => fromCart.getAddedIds(state.cart)
11 const getQuantity = (state, id) => fromCart.getQuantity(state.cart, id)
12 const getProduct = (state, id) => fromProducts.getProduct(state.products, id) //方法调用
13 
14 export const getTotal = state =>
15   getAddedIds(state)
16     .reduce((total, id) =>
17       total + getProduct(state, id).price * getQuantity(state, id),
18       0
19     )
20     .toFixed(2)  //计算总价   
21 
22 export const getCartProducts = state =>
23   getAddedIds(state).map(id => ({
24     ...getProduct(state, id),
25     quantity: getQuantity(state, id)  //得到购物车产品和数量
26   }))

cart.js

 1 import React, { PropTypes } from 'react'
 2 import Product from './Product'
 3 
 4 const Cart  = ({ products, total, onCheckoutClicked }) => {
 5   const hasProducts = products.length > 0
 6   const nodes = hasProducts ? (
 7     products.map(product =>
 8       <Product
 9         title={product.title}
10         price={product.price}
11         quantity={product.quantity}
12         key={product.id}        //product组建 产品的title 数量 价格
13       />
14     )
15   ) : (
16     <em>Please add some products to cart.</em>  //无产品时提示
17   )
18 
19   return (
20     <div>
21       <h3>Your Cart</h3>
22       <div>{nodes}</div>
23       <p>Total: &#36;{total}</p>
24       <button onClick={onCheckoutClicked}
25         disabled={hasProducts ? '' : 'disabled'}>   //购物车没产品时不能点击
26         Checkout
27       </button>
28     </div>
29   )
30 }
31 
32 Cart.propTypes = {
33   products: PropTypes.array,
34   total: PropTypes.string,
35   onCheckoutClicked: PropTypes.func
36 }
37 
38 export default Cart
39 Contact GitHub API Training Shop Blog About

cartcontainer.js

 1 import React, { PropTypes } from 'react'
 2 import { connect } from 'react-redux'
 3 import { checkout } from '../actions'
 4 import { getTotal, getCartProducts } from '../reducers'
 5 import Cart from '../components/Cart'
 6 
 7 const CartContainer = ({ products, total, checkout }) => (
 8   <Cart
 9     products={products}
10     total={total}
11     onCheckoutClicked={() => checkout(products)} />   
12 )
13 
14 CartContainer.propTypes = {
15   products: PropTypes.arrayOf(PropTypes.shape({
16     id: PropTypes.number.isRequired,
17     title: PropTypes.string.isRequired,
18     price: PropTypes.number.isRequired,
19     quantity: PropTypes.number.isRequired
20   })).isRequired,
21   total: PropTypes.string,
22   checkout: PropTypes.func.isRequired
23 }
24 
25 const mapStateToProps = (state) => ({
26   products: getCartProducts(state),  //这里得到得到的产品 就是上面的参数
27   total: getTotal(state) //总价也一样
28 })
29 
30 export default connect(
31   mapStateToProps,
32   { checkout }
33 )(CartContainer)

product.js

 1 import React, { PropTypes } from 'react'
 2 
 3 const Product = ({ price, quantity, title }) => (
 4   <div>
 5     {title} - &#36;{price}{quantity ? ` x ${quantity}` : null} //有数量就算出总价  
 6   </div>
 7 )
 8 
 9 Product.propTypes = {
10   price: PropTypes.number,
11   quantity: PropTypes.number,
12   title: PropTypes.string
13 }
14 
15 export default Product

productItem.js

 1 import React, { PropTypes } from 'react'
 2 import Product from './Product'
 3 
 4 const ProductItem = ({ product, onAddToCartClicked }) => (
 5   <div style={{ marginBottom: 20 }}>
 6     <Product
 7       title={product.title}
 8       price={product.price} />
 9     <button
10       onClick={onAddToCartClicked}
11       disabled={product.inventory > 0 ? '' : 'disabled'}>  // 能否添加购物车
12       {product.inventory > 0 ? 'Add to cart' : 'Sold Out'} //有库存  无库存就时soldout
13     </button>
14   </div>
15 )
16 
17 ProductItem.propTypes = {
18   product: PropTypes.shape({
19     title: PropTypes.string.isRequired,
20     price: PropTypes.number.isRequired,
21     inventory: PropTypes.number.isRequired
22   }).isRequired,
23   onAddToCartClicked: PropTypes.func.isRequired
24 }
25 
26 export default ProductItem

productlist.js

 1 import React, { PropTypes } from 'react'
 2 
 3 const ProductsList = ({ title, children }) => (
 4   <div>
 5     <h3>{title}</h3>
 6     <div>{children}</div>
 7   </div>
 8 )
 9 
10 ProductsList.propTypes = {
11   children: PropTypes.node,
12   title: PropTypes.string.isRequired
13 }
14 
15 export default ProductsList

productcontainer.js

 1 import React, { PropTypes } from 'react'
 2 import { connect } from 'react-redux'
 3 import { addToCart } from '../actions'
 4 import { getVisibleProducts } from '../reducers/products'
 5 import ProductItem from '../components/ProductItem'
 6 import ProductsList from '../components/ProductsList'
 7 
 8 const ProductsContainer = ({ products, addToCart }) => (
 9   <ProductsList title="Products">
10     {products.map(product =>
11       <ProductItem
12         key={product.id}
13         product={product}
14         onAddToCartClicked={() => addToCart(product.id)} />  //这就是  children
15     )}
16   </ProductsList>
17 )
18 
19 ProductsContainer.propTypes = {
20   products: PropTypes.arrayOf(PropTypes.shape({
21     id: PropTypes.number.isRequired,
22     title: PropTypes.string.isRequired,
23     price: PropTypes.number.isRequired,
24     inventory: PropTypes.number.isRequired
25   })).isRequired,
26   addToCart: PropTypes.func.isRequired
27 }
28 
29 const mapStateToProps = state => ({
30   products: getVisibleProducts(state.products) //products的来源
31 })
32 
33 export default connect(
34   mapStateToProps,
35   { addToCart }
36 )(ProductsContainer)

app.js

 1 import React from 'react'
 2 import ProductsContainer from './ProductsContainer'
 3 import CartContainer from './CartContainer'
 4 
 5 const App = () => (
 6   <div>
 7     <h2>Shopping Cart Example</h2>
 8     <hr/>
 9     <ProductsContainer />
10     <hr/>
11     <CartContainer />
12   </div>
13 )
14 
15 export default App

到这里应该差不多看明白了,组件还是很容易看的,主要是action之间的衔接确实。。。

index.html

 1 import React from 'react'
 2 import { render } from 'react-dom'
 3 import { createStore, applyMiddleware } from 'redux'
 4 import { Provider } from 'react-redux'
 5 import createLogger from 'redux-logger'
 6 import thunk from 'redux-thunk'
 7 import reducer from './reducers'
 8 import { getAllProducts } from './actions'
 9 import App from './containers/App'
10 
11 const middleware = [ thunk ];
12 if (process.env.NODE_ENV !== 'production') {
13   middleware.push(createLogger());
14 }
15 
16 const store = createStore(
17   reducer,
18   applyMiddleware(...middleware)
19 )
20 
21 store.dispatch(getAllProducts())  //初始化产品
22 
23 render(
24   <Provider store={store}>
25     <App />
26   </Provider>,
27   document.getElementById('root')
28 )

在我看来 redux的精华就是reducers,这点也是很难掌握的,组件到比较容易,今后多多研究reducer和action。

 

posted @ 2016-10-14 23:26  coder_231  阅读(3411)  评论(0编辑  收藏  举报