前端數(shù)據(jù)流之Redux篇(案例)

create-react-app cart
npm i redux react-redux redux-thunk

  • 結(jié)構(gòu)如下


    頁面結(jié)構(gòu)圖
  • 代碼如下

acions > index.js

import * as shop from '../api/shop'

export const reciveProducts = products => ({
  type: 'RECIVE_PRODUCTS',
  products
})
export const addToCart = product => ({
  type: 'ADD_TO_CART',
  product
})
export const getAllProducts = () => dispatch => {
  shop.getAllProducts(products => {
    dispatch(reciveProducts(products))
  })
}

export const setCheckoutStatus = status => ({
  type: 'SET_CHECKOUT_STATUS',
  status
})
export const setCarItems = items => ({
  type: 'SET_ITEMS',
  items
})
export const checkout = (products) => dispatch => {
  // 1.備份購物車數(shù)據(jù)羹与,結(jié)算失敗好返回
  const tempCartPro = [...products]
  // 2.清空結(jié)算狀態(tài)
  dispatch(setCheckoutStatus(null))
  // 3.清空購物車
  dispatch(setCarItems([]))
  // 4.執(zhí)行結(jié)算操作
  shop.buyProducts(
    products,
    // 成功
    () => dispatch(setCheckoutStatus('success')),
    // 失敗
    () => {
      dispatch(setCheckoutStatus('fail'))
      dispatch(setCarItems(tempCartPro))
    }
  )
}

api > shop.js

const _products = [
  {"id": 1, "title": "9.9包郵華為手機(jī)", "price": 9.9, "inventory": 3},
  {"id": 2, "title": "樂高積木馬里奧超級馬里奧周邊", "price": 1119.9, "inventory": 12},
  {"id": 3, "title": "ps4荒野大鏢客不好玩免單", "price": 229.9, "inventory": 5},
]
// 模擬接口
export const getAllProducts = callback => {
  setTimeout(() => {
    callback(_products)
  }, 1000);
}
export const buyProducts = (products, callback, errCallback) => {
  setTimeout(() => {
    Math.random() > 0.5 ? callback() : errCallback()
  }, 500);
}

components > Cart.js

import React, { Component } from 'react'

class Cart extends Component {
  render() {
    const {cartProducts, totalPrice, checkout, checkoutStatus} = this.props
    return (
      <div>
        <h2>Cart</h2>
        <ul>
          {cartProducts.map(item => (
            <li key={item.id}>
              {item.title}-{item.price}-{item.quantity}
            </li>
          ))}
        </ul>
        {!cartProducts.length && <p>請?zhí)砑由唐返劫徫镘?lt;/p>}
        <div>總價格是:{totalPrice}</div>
        <br/>
        <button
        disabled={!cartProducts.length}
        onClick={() => checkout(cartProducts)}>
          結(jié)算
        </button>
        <br/>
        {checkoutStatus && <div>結(jié)算信息{checkoutStatus}</div>}
      </div>
    )
  }
}

export default Cart

components > Products.js

import React, { Component } from 'react'

class Products extends Component {
  render() {
    const { products } = this.props
    return (
      <div>
        <h2>products</h2>
        <ul>
          {products.map(item => (
            <li key={item.id}>
              {item.title}-{item.price}-{item.inventory}
              <br />
              <button
                disabled={!item.inventory}
                onClick={() => this.props.addToCart(item)}>
                {item.inventory ? '添加到購物車' : '庫存為0'}
              </button>
            </li>
          ))}
        </ul>
      </div>
    )
  }
  componentDidMount() {
    this.props.getAllProducts()
  }
}

export default Products

container > CartContainer.js

import { connect } from 'react-redux'
import Cart from '../components/Cart'
import {checkout} from '../actions'

// 將products中的數(shù)據(jù)映射到cart中來
const getCartProducts = state => {
  return state.cart.items.map( cartItem => {
    const product = state.products.all.find(proItem => proItem.id === cartItem.id)
    return {
      id: product.id,
      title: product.title,
      price: product.price,
      quantity: cartItem.quantity
    }
  })
}
// 獲取總價
const getTotalPrice = state => {
  return getCartProducts(state).reduce((total, product) => {
    return total + product.price * product.quantity
  }, 0) 
}

const mapStateToProps = state => {
  return {
    cartProducts: getCartProducts(state),
    totalPrice: getTotalPrice(state),
    checkoutStatus: state.cart.checkoutStatus
  }
}

const mapDispatchToProps = {
    checkout
}

const CartContainer = connect(
  mapStateToProps,
  mapDispatchToProps
) (Cart)

export default CartContainer

container > ProductsContainer.js

import { connect } from 'react-redux'
import Products from '../components/Products'
import { getAllProducts, addToCart } from '../actions'

const mapStateToProps = state => {
  return {
    products: state.products.all
  }
}

const mapDispatchToProps = {
  getAllProducts,
  addToCart
}

const ProductsContainer = connect(
  mapStateToProps,
  mapDispatchToProps
) (Products)

export default ProductsContainer

reducers > cart.js

const initialState = {
  items: [],
  checkoutStatus: null
}
const items = (state = initialState.items, action) => {
  switch (action.type) {
    case 'ADD_TO_CART':
      // 判斷購物車存在商品嗎
      // 存在 - quantity+1
      // 不存在 - 添加商品到購物車
      const productId = action.product.id
      const product = state.find(item => item.id === productId)
      if (product) {
        product.quantity++
        return [...state]
      } else {
        return [...state, {
          id: productId,
          quantity: 1
        }]
      }
    case 'SET_ITEMS':
      return action.items
    default:
      return state
  }
}
const checkoutStatus = (state = initialState.checkoutStatus, action) => {
  switch (action.type) {
    case 'SET_CHECKOUT_STATUS':
      return action.status
    default:
      return state
    }
}
export default (state = initialState, action) => {
  return {
    items: items(state.items, action),
    checkoutStatus: checkoutStatus(state.checkoutStatus, action)
  }
}

reducers > products.js

const initialState = {
  all: []
}
const all = (state = initialState.all, action) => {
  switch (action.type) {
    case 'RECIVE_PRODUCTS':
      return action.products
    case 'ADD_TO_CART':
      const productId = action.product.id
      const product = state.find(item => item.id === productId)
      product.inventory--
      return [...state]
    default:
      return state
  }
}

export default (state = initialState, action) => {
  return {
    all: all(state.all, action),
  }
}

store > index.js

import { createStore, combineReducers, applyMiddleware, compose  } from 'redux'
import thunk from 'redux-thunk'
import products from '../reducers/products'
import cart from '../reducers/cart'

const rootReducer = combineReducers({
  products,
  cart
})
const composeEnhancers =
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;

const enhancer = composeEnhancers(
  applyMiddleware(thunk)
)

const store = createStore(
  rootReducer,
  // 啟用redux調(diào)試工具
  enhancer
)
export default store
  • 優(yōu)化與建議
    action.type里面的值是字符串恒傻,這樣每次使用dispatch的時候荆忍,輸入的參數(shù)是字符串凰慈,容易出錯汛蝙。
    解決辦法:
    定義一個公用文件省咨,把action.type保存進(jìn)去吼肥,每次使用的時候乌叶,將字符串的部分替換成對應(yīng)的變量即可跷敬。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末讯私,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子西傀,更是在濱河造成了極大的恐慌斤寇,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拥褂,死亡現(xiàn)場離奇詭異娘锁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)饺鹃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門莫秆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人悔详,你說我怎么就攤上這事镊屎。” “怎么了茄螃?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵缝驳,是天一觀的道長。 經(jīng)常有香客問我归苍,道長用狱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任拼弃,我火速辦了婚禮夏伊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吻氧。我一直安慰自己溺忧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布医男。 她就那樣靜靜地躺著砸狞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪镀梭。 梳的紋絲不亂的頭發(fā)上刀森,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天,我揣著相機(jī)與錄音报账,去河邊找鬼研底。 笑死埠偿,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的榜晦。 我是一名探鬼主播冠蒋,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼乾胶!你這毒婦竟也來了抖剿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤识窿,失蹤者是張志新(化名)和其女友劉穎斩郎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喻频,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缩宜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了甥温。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锻煌。...
    茶點(diǎn)故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖姻蚓,靈堂內(nèi)的尸體忽然破棺而出宋梧,到底是詐尸還是另有隱情,我是刑警寧澤狰挡,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布乃秀,位于F島的核電站,受9級特大地震影響圆兵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜枢贿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一殉农、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧局荚,春花似錦超凳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至首装,卻和暖如春创夜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背仙逻。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工驰吓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涧尿,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓檬贰,卻偏偏與公主長得像姑廉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子翁涤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評論 2 348