react + redux 之 美团案例

1.案例展示

![](https://img-blog.csdnimg.cn/direct/b7a9604e5d274504ad630427a996aa8b.png
在这里插入图片描述

2.环境搭建

  1. 克隆项目到本地(内置了基础静态组件和模版)
git clone http://git.itcast.cn/heimaqianduan/redux-meituan.git 
  1. 安装所有依赖
npm i 
  1. 启动mock服务(内置了json-server)
npm run serve 
  1. 启动前端服务
npm run start 

3.分类和商品列表渲染

在这里插入图片描述
1.store modules 下 takeaway.js文件

// 编写store
import {
    
     createSlice } from "@reduxjs/toolkit"
import axios from "axios"

const foodsStore = createSlice({
    
    
  name: 'foods',
  initialState: {
    
    
    // 商品列表
    foodsList: [],a
  },
  reducers: {
    
    
    // 更改商品列表
    setFoodsList (state, action) {
    
    
      state.foodsList = action.payload
    }
  }
})

// 异步获取部分
const {
    
     setFoodsList} = foodsStore.actions
const fetchFoodsList = () => {
    
    
  return async (dispatch) => {
    
    
    // 编写异步逻辑
    const res = await axios.get('http://localhost:3004/takeaway')
    // 调用dispatch函数提交action
    dispatch(setFoodsList(res.data))
  }
}

export {
    
     fetchFoodsList }

const reducer = foodsStore.reducer
export default reducer

2.store下index.js文件

import foodsReducer from './modules/takeaway'
import {
    
     configureStore } from '@reduxjs/toolkit'
const store = configureStore({
    
    
  reducer: {
    
    
    foods: foodsReducer
  }
})
export default store

3.app.js

import {
    
     useDispatch, useSelector } from 'react-redux'
import {
    
     fetchFoodsList } from './store/modules/takeaway'
import {
    
     useEffect } from 'react'
// 触发action执行
  // 1. useDispatch -> dispatch 2. actionCreater导入进来 3.useEffect
  const dispatch = useDispatch()
  useEffect(() => {
    
    
    dispatch(fetchFoodsList())
  }, [dispatch])
  // 获取foodsList渲染数据列表
  // 1. useSelector
  const {
    
     foodsList } = useSelector(state => state.foods)
   {
    
    /* 外卖商品列表 */}
              {
    
    foodsList.map((item, index) => {
    
    
                return (
                 <FoodsCategory
                    key={
    
    item.tag}
                    // 列表标题
                    name={
    
    item.name}
                    // 列表商品
                    foods={
    
    item.foods}
                  />
                )
              })}

4.menu.js

import {
    
     useDispatch,useSelector } from 'react-redux'
const dispatch = useDispatch()
const {
    
    foodsList} = useSelector(state=>state.foods) 

5.index.js

// 注入store
import {
    
     Provider } from 'react-redux'
import store from './store'
const root = createRoot(document.getElementById('root'))
root.render(
  <Provider store={
    
    store}>
    <App />
  </Provider>
)

4.点击分类激活实现

在这里插入图片描述
1.store modules下 takeaway.js文件


// 编写store
import {
    
     createSlice } from "@reduxjs/toolkit"
import axios from "axios"
const foodsStore = createSlice({
    
    
  name: 'foods',
  initialState: {
    
    
    // 商品列表
    foodsList: [],
    //激活index
    activeIndex:0,
  },
  reducers: {
    
    
    // 更改商品列表
    setFoodsList (state, action) {
    
    
      state.foodsList = action.payload
    },
    //更改activeIndex
    changeActiveIndex(state,action){
    
    
        state.activeIndex = action.payload
    }
  }
})

// 异步获取部分
const {
    
     setFoodsList,changeActiveIndex} = foodsStore.actions

2.menu.js

import classNames from 'classnames'
import './index.scss'

import {
    
     useDispatch,useSelector } from 'react-redux'
import {
    
     changeActiveIndex} from '../../store/modules/takeaway'
const Menu = () => {
    
    
  const dispatch = useDispatch()
  const {
    
    foodsList,activeIndex} = useSelector(state=>state.foods) 
  const menus = foodsList.map(item => ({
    
     tag: item.tag, name: item.name }))
  return (
    <nav className="list-menu">
      {
    
    /* 添加active类名会变成激活状态 */}
      {
    
    menus.map((item, index) => {
    
    
        return (
          <div
          onClick={
    
    () => dispatch(changeActiveIndex(index))}
            key={
    
    item.tag}
            className={
    
    classNames(
              'list-menu-item',
              activeIndex === index && 'active'
            )}
          >
            {
    
    item.name}
          </div>
        )
      })}
    </nav>
  )
}

export default Menu

3.app.js

const {
    
     foodsList , activeIndex} = useSelector(state => state.foods)
<div className="goods-list">
              {
    
    /* 外卖商品列表 */}
              {
    
    foodsList.map((item, index) => {
    
    
                return (
                  activeIndex==index && <FoodsCategory
                    key={
    
    item.tag}
                    // 列表标题
                    name={
    
    item.name}
                    // 列表商品
                    foods={
    
    item.foods}
                  />
                )
              })}
            </div>

5.添加购物车

在这里插入图片描述
1.takeaway.js

// 编写store

import {
    
     createSlice } from "@reduxjs/toolkit"
import axios from "axios"

const foodsStore = createSlice({
    
    
  name: 'foods',
  initialState: {
    
    
    // 商品列表
    foodsList: [],
    // 菜单激活下标值
    activeIndex: 0,
    // 购物车列表
    cartList: []
  },
  reducers: {
    
    
    // 更改商品列表
    setFoodsList (state, action) {
    
    
      state.foodsList = action.payload
    },
    // 更改activeIndex
    changeActiveIndex (state, action) {
    
    
      state.activeIndex = action.payload
    },
    // 添加购物车
    addCart (state, action) {
    
    
      // 是否添加过?以action.payload.id去cartList中匹配 匹配到了 添加过
      const item = state.cartList.find(item => item.id === action.payload.id)
      if (item) {
    
    
        item.count++
      } else {
    
    
        state.cartList.push(action.payload)
      }
    },
  }
})

const {
    
     setFoodsList, changeActiveIndex, addCart} = foodsStore.actions
export {
    
     fetchFoodsList, changeActiveIndex, addCart}

2.foodItem下index.js文件

import {
    
     useDispatch } from 'react-redux'
import {
    
     setCarlist } from '../../../store/modules/takeaway'
const dispatch = useDispatch()
 <div className="goods-count">
            <span className="plus" onClick={
    
    () => dispatch(setCarlist({
    
    
              id,
              picture,
              name,
              unit,
              description,
              food_tag_list,
              month_saled,
              like_ratio_desc,
              price,
              tag,
              count
            }))}></span>
          </div>

6.统计区域功能实现

在这里插入图片描述
在这里插入图片描述
1.cart下面index.js

import classNames from 'classnames'
import {
    
     useState } from 'react'
import {
    
     useDispatch, useSelector } from 'react-redux'
import Count from '../Count'
import './index.scss'


const Cart = () => {
    
    
  const {
    
     carList } = useSelector(state => state.foods)
  // 计算总价 
  const totalPrice = carList.reduce((a, c) => a + c.price * c.count, 0)
  return (
    <div className="cartContainer">
      <div className="cart">
        {
    
    /* fill 添加fill类名购物车高亮*/}
        {
    
    /* 购物车数量 */}
        <div  className={
    
    classNames('icon', carList.length > 0 && 'fill')}>
          {
    
    carList.length > 0 && <div className="cartCornerMark">{
    
    carList.length}</div>}
        </div>
        {
    
    /* 购物车价格 */}
        <div className="main">
          <div className="price">
            <span className="payableAmount">
              <span className="payableAmountUnit">¥</span>
              {
    
    totalPrice.toFixed(2)}
            </span>
          </div>
          <span className="text">预估另需配送费 ¥5</span>
        </div>
        {
    
    /* 结算 or 起送 */}
        {
    
    carList.length > 0 ? (
          <div className="goToPreview">去结算</div>
        ) : (
          <div className="minFee">1元起送</div>
        )}
      </div>
      {
    
    /* 添加visible类名 div会显示出来 */}
      <div className={
    
    classNames('cartPanel')}>
        <div className="header">
          <span className="text">购物车</span>
          <span className="clearCart">
            清空购物车
          </span>
        </div>

        {
    
    /* 购物车列表 */}
        <div className="scrollArea">
          {
    
    carList.map(item => {
    
    
            return (
              <div className="cartItem" key={
    
    item.id}>
                <img className="shopPic" src={
    
    item.picture} alt="" />
                <div className="main">
                  <div className="skuInfo">
                    <div className="name">{
    
    item.name}</div>
                  </div>
                  <div className="payableAmount">
                    <span className="yuan">¥</span>
                    <span className="price">{
    
    item.price}</span>
                  </div>
                </div>
                <div className="skuBtnWrapper btnGroup">
                  {
    
    /* 数量组件 */}
                  <Count
                    count={
    
    item.count}
                 
                  />
                </div>
              </div>
            )
          })}
        </div>
      </div>
    </div>
  )
}

export default Cart


7.购物车列表功能实现

在这里插入图片描述
在这里插入图片描述
1.takeaway.js

// 编写store

import {
    
     createSlice } from "@reduxjs/toolkit"
import axios from "axios"

const foodsStore = createSlice({
    
    
  name: 'foods',
  initialState: {
    
    
    // 商品列表
    foodsList: [],
    //激活index
    activeIndex:0,
    //汽车
    carList:[]
  },
  reducers: {
    
    
    // 更改商品列表
    setFoodsList (state, action) {
    
    
      state.foodsList = action.payload
    },
    //更改activeIndex
    changeActiveIndex(state,action){
    
    
        state.activeIndex = action.payload
    },
    setCarlist(state,action){
    
    
      // 是否添加过?以action.payload.id去cartList中匹配 匹配到了 添加过
      const item = state.carList.find(item => item.id === action.payload.id)
      if (item) {
    
    
        item.count++
      } else {
    
    
        state.carList.push(action.payload)
      }
    },
    increCount(state,action){
    
    
      const item = state.carList.find(item => item.id === action.payload.id)
      item.count++
    },
    decreCount(state,action){
    
    
        const item = state.carList.find(item => item.id === action.payload.id)
        if(item.count===0){
    
    
         return
        }
      item.count--
    },
     // 清除购物车
     clearCart (state) {
    
    
        state.carList = []
      }
    
}
})

// 异步获取部分
const {
    
     setFoodsList,changeActiveIndex,setCarlist,increCount,decreCount,clearCart} = foodsStore.actions
const fetchFoodsList = () => {
    
    
  return async (dispatch) => {
    
    
    // 编写异步逻辑
    const res = await axios.get('http://localhost:3004/takeaway')
    // 调用dispatch函数提交action
    dispatch(setFoodsList(res.data))
  }
}

export {
    
     fetchFoodsList ,changeActiveIndex,setCarlist,increCount,decreCount,clearCart}

const reducer = foodsStore.reducer

export default reducer

2.cart下index文件

import classNames from 'classnames'
import {
    
     useDispatch, useSelector } from 'react-redux'
import Count from '../Count'
import './index.scss'
import {
    
    increCount,decreCount,clearCart} from '../../store/modules/takeaway'


const Cart = () => {
    
    
  const {
    
     carList } = useSelector(state => state.foods)
  // 计算总价 
  const totalPrice = carList.reduce((a, c) => a + c.price * c.count, 0)

  const dispatch = useDispatch()

  return (
    <div className="cartContainer">
      <div className="cart">
        {
    
    /* fill 添加fill类名购物车高亮*/}
        {
    
    /* 购物车数量 */}
        <div  className={
    
    classNames('icon', carList.length > 0 && 'fill')}>
          {
    
    carList.length > 0 && <div className="cartCornerMark">{
    
    carList.length}</div>}
        </div>
        {
    
    /* 购物车价格 */}
        <div className="main">
          <div className="price">
            <span className="payableAmount">
              <span className="payableAmountUnit">¥</span>
              {
    
    totalPrice.toFixed(2)}
            </span>
          </div>
          <span className="text">预估另需配送费 ¥5</span>
        </div>
        {
    
    /* 结算 or 起送 */}
        {
    
    carList.length > 0 ? (
          <div className="goToPreview">去结算</div>
        ) : (
          <div className="minFee">1元起送</div>
        )}
      </div>
      {
    
    /* 添加visible类名 div会显示出来 */}
      <div className={
    
    classNames('cartPanel',carList.length>0&&'visible')} >
        <div className="header">
          <span className="text">购物车</span>
          <span className="clearCart" onClick={
    
    ()=>dispatch(clearCart())}>
            清空购物车
          </span>
        </div>

        {
    
    /* 购物车列表 */}
        <div className="scrollArea">
          {
    
    carList.map(item => {
    
    
            return (
              <div className="cartItem" key={
    
    item.id}>
                <img className="shopPic" src={
    
    item.picture} alt="" />
                <div className="main">
                  <div className="skuInfo">
                    <div className="name">{
    
    item.name}</div>
                  </div>
                  <div className="payableAmount">
                    <span className="yuan">¥</span>
                    <span className="price">{
    
    item.price}</span>
                  </div>
                </div>
                <div className="skuBtnWrapper btnGroup">
                  {
    
    /* 数量组件 */}
                  <Count
                    count={
    
    item.count}
                    onPlus={
    
    ()=>dispatch(increCount({
    
    id:item.id}))}
                    onMinus={
    
    ()=>dispatch(decreCount({
    
    id:item.id}))}
                  />
                </div>
              </div>
            )
          })}
        </div>
      </div>
    </div>
  )
}

export default Cart

8.控制购物车显示和隐藏

在这里插入图片描述

在这里插入图片描述
1.cart文件下index.js文件

import classNames from 'classnames'
import {
    
     useDispatch, useSelector} from 'react-redux'
import {
    
     useState } from 'react'
import Count from '../Count'
import './index.scss'
import {
    
    increCount,decreCount,clearCart} from '../../store/modules/takeaway'


const Cart = () => {
    
    
  const {
    
     carList } = useSelector(state => state.foods)
  // 计算总价 
  const totalPrice = carList.reduce((a, c) => a + c.price * c.count, 0)
  const [visible,setVisible]= useState(false)
  const dispatch = useDispatch()

  const onShow = () => {
    
    
    if (carList.length > 0) {
    
    
      setVisible(true)
    }
  }
  return (
    <div className="cartContainer">
       {
    
    /* 遮罩层 添加visible类名可以显示出来 */}
       <div
        className={
    
    classNames('cartOverlay', visible && 'visible')}
        onClick={
    
    () => setVisible(false)}
      />
      <div className="cart">
        {
    
    /* fill 添加fill类名购物车高亮*/}
        {
    
    /* 购物车数量 */}
        <div onClick={
    
    onShow}  className={
    
    classNames('icon', carList.length > 0 && 'fill')}>
          {
    
    carList.length > 0 && <div className="cartCornerMark">{
    
    carList.length}</div>}
        </div>
        {
    
    /* 购物车价格 */}
        <div className="main">
          <div className="price">
            <span className="payableAmount">
              <span className="payableAmountUnit">¥</span>
              {
    
    totalPrice.toFixed(2)}
            </span>
          </div>
          <span className="text">预估另需配送费 ¥5</span>
        </div>
        {
    
    /* 结算 or 起送 */}
        {
    
    carList.length > 0 ? (
          <div className="goToPreview">去结算</div>
        ) : (
          <div className="minFee">1元起送</div>
        )}
      </div>
      {
    
    /* 添加visible类名 div会显示出来 */}
      <div className={
    
    classNames('cartPanel',visible &&'visible')} >
        <div className="header">
          <span className="text">购物车</span>
          <span className="clearCart" onClick={
    
    ()=>dispatch(clearCart())}>
            清空购物车
          </span>
        </div>

        {
    
    /* 购物车列表 */}
        <div className="scrollArea">
          {
    
    carList.map(item => {
    
    
            return (
              <div className="cartItem" key={
    
    item.id}>
                <img className="shopPic" src={
    
    item.picture} alt="" />
                <div className="main">
                  <div className="skuInfo">
                    <div className="name">{
    
    item.name}</div>
                  </div>
                  <div className="payableAmount">
                    <span className="yuan">¥</span>
                    <span className="price">{
    
    item.price}</span>
                  </div>
                </div>
                <div className="skuBtnWrapper btnGroup">
                  {
    
    /* 数量组件 */}
                  <Count
                    count={
    
    item.count}
                    onPlus={
    
    ()=>dispatch(increCount({
    
    id:item.id}))}
                    onMinus={
    
    ()=>dispatch(decreCount({
    
    id:item.id}))}
                  />
                </div>
              </div>
            )
          })}
        </div>
      </div>
    </div>
  )
}

export default Cart

猜你喜欢

转载自blog.csdn.net/weixin_42268006/article/details/135269686
今日推荐