用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: ${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} - ${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。