React 第三十节 使用 useState 和 useEffect Hook实现购物车

不使用 redux 实现 购物车案例

使用 React 自带的 useState 和 useEffect Hook 即可实现购物车

javascript 复制代码
export default function ShoppingCar() {
    // 要结算的商品 总数 以及总价
    const [totalNum, setTotalNum] = useState(0)
    const [totalPerice, setTotalPerice] = useState(0)
    // 商品列表 初始化一个mock 数据
    const [list, setList] = useState(orderLists)
    ...
}

1、新增商品

使用 setList() 进数据状态更新

javascript 复制代码
// 模拟新增商品
const handleAdd = () => {
    const curList = [...list]
    console.log('===curList=', curList)
    curList.unshift({
        name: `商品${list.length + 1}`,
        id: `sss_${list.length + 1}` ,
        num: 1,
        price: 12.00,
        totalPrice: 12.00, 
    })
    setList([...curList])
}

2、修改商品的数量

javascript 复制代码
const handleChangeNum = (type, id) => {
    const curList = [...list]
    if(type === 'ADD') {
        curList.map(itm => {
        if (itm.id === id) {
            itm.num = itm.num + 1
            itm.totalPrice = itm.num * itm.price
        }
        })
        
    } else{
        curList.map(itm => {
            if (itm.id === id) {
            itm.num = itm.num !== 0  ? itm.num - 1 : 0
            itm.totalPrice = itm.num * itm.price
            }
        })
    }
    setList([...curList])
}

3、选择要结算的商品

javascript 复制代码
const handleChangeCheckbox = (e, id) =>{
    console.log('---', e.target.value)
    const curList = [...list]
    curList.map(itm => {
        if (itm.id === id) {
        itm.isChecked = !itm.isChecked
        }
    })
    setList([...curList])
}

4、计算要结算商品的 总数 及 总价

使用 useEffect() 监测 商品列表的变化

javascript 复制代码
useEffect(() => {
    let isSelectedLists = []
    list.map(itm => {
    if (itm.isChecked) {
        isSelectedLists.push(itm)
    }
    })
    console.log('=isSelectedLists==', isSelectedLists)
    const curNum = isSelectedLists && isSelectedLists.length &&isSelectedLists.reduce((total, item) => total + item.num, 0) || 0
    const curTotal = isSelectedLists && isSelectedLists.length && isSelectedLists.reduce((total, item) => total + item.totalPrice, 0) || 0
    console.log('==curNum==', curNum)
    console.log('==curTotal==', curTotal)
    setTotalNum(curNum)
    setTotalPerice(curTotal)
}, [list]

5、完整代码案例

javascript 复制代码
// index.jsx 
import React, {useState, useEffect, useId} from 'react'
import './index.scss'
import { orderLists } from './mock.js'
export default function ShoppingCar() {
  // const id = useId()
    const [totalNum, setTotalNum] = useState(0)
    const [totalPerice, setTotalPerice] = useState(0)
    const [list, setList] = useState(orderLists)
    const handleChangeNum = (type, id) => {
      const curList = [...list]
        if(type === 'ADD') {
          curList.map(itm => {
            if (itm.id === id) {
              itm.num = itm.num + 1
              itm.totalPrice = itm.num * itm.price
            }
          })
            
        } else{
            curList.map(itm => {
              if (itm.id === id) {
                itm.num = itm.num !== 0  ? itm.num - 1 : 0
                itm.totalPrice = itm.num * itm.price
              }
            })
        }
        setList([...curList])
    }
    const handleChangeCheckbox = (e, id) =>{
      console.log('---', e.target.value)
        const curList = [...list]
        curList.map(itm => {
          if (itm.id === id) {
            itm.isChecked = !itm.isChecked
          }
        })
        setList([...curList])
    }

    const handleAdd = () => {
      const curList = [...list]
      console.log('===curList=', curList)
      curList.unshift({
          name: `商品${list.length + 1}`,
          id: `sss_${list.length + 1}` ,
          num: 1,
          price: 12.00,
          totalPrice: 12.00, 
      })
      setList([...curList])
    }

    
    useEffect(() => {
      let isSelectedLists = []
      list.map(itm => {
        if (itm.isChecked) {
          isSelectedLists.push(itm)
        }
      })
      console.log('=isSelectedLists==', isSelectedLists)
     const curNum = isSelectedLists && isSelectedLists.length &&isSelectedLists.reduce((total, item) => total + item.num, 0) || 0
     const curTotal = isSelectedLists && isSelectedLists.length && isSelectedLists.reduce((total, item) => total + item.totalPrice, 0) || 0
     console.log('==curNum==', curNum)
     console.log('==curTotal==', curTotal)
     setTotalNum(curNum)
     setTotalPerice(curTotal)
    }, [list])

  return (
    <div className='list'>
      {
        list.map(itm => {
          return (
            <div className="li" key={itm.id}>
              <div className='commodity'>
                  <input type="checkbox" name="" id="" value={itm.isChecked} onClick={(e) => handleChangeCheckbox(e, itm.id)}/>
                  <span>{itm.name}</span>
              </div>
              <div className="price">单价:{itm.price}</div>
              <div className='num'>
                  <span className='handle-icon' onClick={() => handleChangeNum('ADD', itm.id)}>+</span>
                  <span className='itm-num'>{itm.num}</span>
                  <span  className='handle-icon' onClick={() => handleChangeNum('REDUCE', itm.id)}>-</span>
              </div>
              <div className='total'>总价:{itm.totalPrice}</div>
            </div>
            )
        })
      }
      
      <div className='total'>
        <span className='total-num'>共计:{totalNum}件</span>
        <span className='total-price'>合计:{totalPerice}元</span>
      </div>

      <button className="btn" onClick={handleAdd}>增加商品</button>
    </div>
  )
}
javascript 复制代码
// index.scss 文件
.li{
    width: 100%;
    height: auto;
    display: flex;
    align-items: left;
    justify-content: left;
}
.commodity{
    width: 100%;
    height: 46px;
    display: flex;
    align-items: center;
    justify-content: left;
    &>span{
        display: inline-block;
        min-width: 100px;
        margin-left: 6px;
        margin-right: 6px;
    }

}
.price{
    display: inline-block;
    margin-left: 6px;
    margin-right: 6px;
    min-width: 90px;
}
.num{
    width: 100%;
    height: 36px;
    display: flex;
    align-items: center;
    cursor: pointer;
}
.handle-icon{
    display: inline-block;
    width: 36px;
    height: 36px;
    cursor: pointer;
    line-height: 36px;
    border: 1px solid #efefef;
}
.itm-num{
    display: inline-block;
    padding: 2px 8px;
}
.total{
    min-width: 100px;
    color: #ff0000;
}
.total-price{
    margin-left: 20px;
}
.btn{
    
}

6、注意事项

a、使用useState 的setXXX() 函数进行修改 对象类型时候,需要修改其指针和值 ,如果不修改指向,不会触发视图更新;

b、列表中每一项需要有唯一性的key,这样才会触发唯一性进行数据更新;

c、使用useState 和 useEffect 实现的购物车,只能在当前页使用,不利于代码复用,并且会随着 购物车里面优惠券、折扣等逻辑的增加,而变的难以维护;

相关推荐
miss34 分钟前
AI 改完不敢上线?OpenSpec 规约驱动开发实战
前端·代码规范·前端工程化
AiMan1 小时前
腾讯ima还是不可以导出左侧所有对话,只能单条导出,方法不对吗
前端
去伪存真1 小时前
并发量一高就装死?Agent 全链路“防猝死”手册
前端·agent
Moment1 小时前
AI 编程时代,聊聊我的 Mac mini 双屏桌面
前端·后端
AiMan1 小时前
先确认能不能一次把Microsofat Copilot所有内容打包一个文件导出
前端
总要学点什么2 小时前
第97篇:三维数据加载-城市建筑白模自定义着色器——让一条光带沿高度扫上去,快慢却由帧率说了算
前端·gis·webgl·cesium·着色器·空间分析·白模
上海懒图科技2 小时前
工厂厂区电子地图定位导航:厂区人车精准定位,安全合规双保障
前端·arcgis·vue
西安小哥2 小时前
端侧跑大模型,真正的门槛不在参数量:一次 596 秒到 10 秒的压缩路径复盘
前端·人工智能
陈随易3 小时前
Cloudflare 出钱,Vite 团队挑人,44 个维护者分 30 万美元
前端·后端·程序员
恋猫de小郭3 小时前
Flutter THLC 的 WebView 滚动终于从 70 FPS 提升到 120 FPS
android·前端·flutter