下载router:npm i react-router-dom
下载redux:npm i @reduxjs/toolkit
下载store:npm i store
下载react-redux:npm i react redux
1.创建组件
2.创建router文件夹,里面创建index.js
import { createBrowserRouter,Navigate } from "react-router-dom";
//导入组件
import List from '../components/List'
import Cart from '../components/Cart'
//路由表
const router = createBrowserRouter([
{
path:'/list',
element:<List/>
},
{
path:'/cart',
element:<Cart/>
},
{
path:'/',
element:<Navigate to='/list'/>
},
])
export default router
3. 在index.js组件内导入router进行使用
import React from 'react';
import ReactDOM from 'react-dom/client';
import { RouterProvider } from 'react-router-dom'
import router from './router';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<RouterProvider router={router}/>
</React.StrictMode>
);
4.创建store文件夹,创建一个modules,创建一个cart.js文件
import { createSlice } from '@reduxjs/toolkit'
// 封装一个数据持久化的方法
const storage = {
setStorage(key,value){
sessionStorage[key] = JSON.stringify(value)
},
getStorage(key){
return JSON.parse(sessionStorage[key] || '[]')
}
}
// 进行切分
const cart = createSlice({
name: 'cart', //名字
initialState:{ //存放数据
cart: storage.getStorage('cart')
},
reducers: { // 把reducers和actions合并在一起了
// 商品加入购物车
addCart(state,{ payload }){
// 判断当前商品在购物车中是否存在
const idx = state.cart.findIndex(item => item.id === payload.id)
if(idx !== -1){ //证明当前商品在购物车中存在
state.cart[idx].num += 1
}else{
state.cart.push(payload)
}
// 本地存储
storage.setStorage('cart',state.cart)
},
//修改购物车商品的状态
changeState(state,{ payload : id}){
// 根据下标找下标
const idx = state.cart.findIndex(item => item.id === id)
// 更改状态
state.cart[idx].checked = !state.cart[idx].checked
// 本地存储
storage.setStorage('cart',state.cart)
},
// 全选
chooseAll(state,{ payload : checked }){
state.cart.forEach(item => {
item.checked = checked
})
// 本地存储
storage.setStorage('cart',state.cart)
},
//修改商品的数量
changeNum(state,{payload}){
// 根据id找下标
const idx = state.cart.findIndex(item => item.id === payload.id)
switch(payload.type){
case 'addNum': //点击的是加号按钮
state.cart[idx].num++
break;
case 'descNum': //点击的是减号按钮
state.cart[idx].num > 1 && state.cart[idx].num--
break;
default:
state.cart[idx].num = payload.num * 1
}
// 本地存储
storage.setStorage('cart',state.cart)
},
// 删除当前商品
delGoods(state,{payload : id}){
// 根据id找下标
const idx = state.cart.findIndex(item => item.id === id)
// 删除当前商品
state.cart.splice(idx,1)
// 本地存储
storage.setStorage('cart',state.cart)
},
// 删除选中商品
delChoose(state){
state.cart = state.cart.filter(item => item.checked === false)
// 本地存储
storage.setStorage('cart',state.cart)
}
}
})
// 写一个reducer就需要导出来一个
export const {
addCart,
changeState,
chooseAll,
changeNum,
delGoods,
delChoose
} = cart.actions
export default cart
5. 在store文件夹中,创建一个index.js文件
import { configureStore } from "@reduxjs/toolkit";
import cart from "./modules/cart"; //导入创建modules中的文件
// 创建store
const store = configureStore({
reducer : cart.reducer
})
export default store
6.在index.js组件内导入store进行使用
import React from 'react';
import ReactDOM from 'react-dom/client';
import { RouterProvider } from 'react-router-dom'
import router from './router';
import { Provider } from 'react-redux'
import store from './store';
import './css/index.css';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
{/* 使用Provider包裹根组件,挂载store,这样每个组件都可以拿到store */}
<Provider store={store}>
<RouterProvider router={router}/>
</Provider>
</React.StrictMode>
);
7.在list文件中
import React,{ useEffect,useState } from 'react'
import { useNavigate } from 'react-router-dom'
import axios from 'axios'
import { useDispatch } from 'react-redux'
import { addCart } from '../../store/modules/cart'
import './index.scss'
export default function List() {
const navigate = useNavigate()
const [list, setlist] = useState([])
// 派发action的
const dispatch = useDispatch()
// 封装获取商品列表数据的方法
const getList = async () => {
const { data } = await axios.get('/data.json')
setlist(data)
}
// 商品加入购物车
const addGppds = goods => {
dispatch(addCart({...goods, num: 1, checked: false}))
}
// 封装渲染商品列表的方法
const renderList = () => (
list.map((item,index)=>(
<li key={index}>
<div className="img-box">
<img src={"/imgs/"+item.img} alt="" />
</div>
<div className="goods-info">
<div className="goods-title">{item.title}</div>
<div className="goods-info-footer">
<div className="goods-price">{item.price}</div>
<div className="add-cart" onClick={ () => addGppds(item) }>加入购物车</div>
</div>
</div>
</li>
))
)
useEffect(()=>{
getList()
},[])
return (
<div className="goods-list">
<div className="goods-list-content">
<div className="go-cart" onClick={() => navigate('/cart')}>去购物车</div>
<ul className="list">{ renderList() }</ul>
</div>
</div>
)
}
8.在Cart文件中
import React,{ useMemo } from 'react'
import { useNavigate } from 'react-router-dom'
import { useSelector,useDispatch } from 'react-redux' //useSelector:获取store模块中保存的数据
import { changeState,chooseAll,changeNum,delGoods,delChoose } from '../../store/modules/cart'
import './index.scss'
export default function Cart() {
const navigate = useNavigate()
// 获取到redux中的数据
const cart = useSelector(store => store.cart)
const dispatch = useDispatch()
// 全选按钮的状态
const checkedAll = useMemo(() => cart.every(item => item.checked))
// 手动修改购物车商品的数量
const updateNum = (ev,id) => {
if(!isNaN(ev.target.value)){
dispatch( changeNum( { type:'changeNum', id: id, num : ev.target.value } ) )
}else{
dispatch( changeNum( { type:'changeNum', id: id, num : 1 } ) )
}
}
// 失去焦点的时候触发
const blur = (ev,id) => {
ev.target.value == 0 && dispatch( changeNum( { type:'changeNum', id, num:1} ) )
}
// 计算当前选中商品的总价钱
const totalPrice = useMemo(()=> {
return cart.filter(item => item.checked).reduce((num,item) => num + item.price * item.num,0)
})
// 计算选中商品的总数量
const totalCount = useMemo(() => {
return cart.filter(item => item.checked).reduce((num,item) => num + item.num,0)
})
// 计算结算按钮的状态
const settlementState = useMemo(() => cart.some(item => item.checked))
// 封装渲染购物车列表的方法
const renderCart = () => (
cart.map(item => (
<li key={ item.id }>
<div className="checkbox">
<input type="checkbox" checked={ item.checked } onChange={ () => dispatch( changeState(item.id) ) } />
</div>
<div className="goods-info">
<div className="img-box">
<img src={"/imgs/" + item.img} alt="" />
</div>
<div className="goods-introduce">{ item.title }</div>
</div>
<div className="goods-price">¥{ (item.price).toFixed(2) }</div>
<div className="goods-controler">
<div
className={ item.num > 1 ? "desc" : "desc disabled" }
onClick={ () => dispatch( changeNum({ type:'descNum', id: item.id }) ) }
>-</div>
<div className="input-wrapper">
<input type="text" value={ item.num } onChange={ ev => updateNum(ev,item.id) } onBlur={ ev => blur(ev,item.id) } />
</div>
<div className="add" onClick={ () => dispatch( changeNum({ type: 'addNum', id: item.id}) ) }>+</div>
</div>
<div className="goods-total">¥{ (item.price * item.num).toFixed(2) }</div>
<div className="del-btn" onClick={() => dispatch(delGoods(item.id))}>删除</div>
</li>
))
)
return (
<div className="cart-wrapper">
<div className="cart-content-wrapper">
<div className="cart-title">
<h3>购物车</h3>
<div className="go-list" onClick={()=>navigate(-1)}>返回商品列表</div>
</div>
{
cart.length ?
<>
<div className="cart-list">
<li>
<div className="checkbox">
<input type="checkbox" />
</div>
<div className="goods-info-title">商品信息</div>
<div className="goods-price-title">单价</div>
<div className="goods-num-title">商品数量</div>
<div className="goods-total-title">金额</div>
<div>操作</div>
</li>
{ renderCart() }
</div>
<div className="footer-wrapper">
<div className="left-box">
<label>
全选:<input type="checkbox" checked={checkedAll} onChange={ ev => dispatch( chooseAll(ev.target.checked) ) }/>
</label>
<div className="del-choose-btn" onClick={()=>dispatch(delChoose())}>删除</div>
</div>
<div className="right-box">
<div className="choose-goods-total">
<span>已选商品</span>
<span>{totalCount}</span>件
</div>
<div className="total-price">合计:<span>{ totalPrice.toFixed(2) }</span></div>
<div className={ settlementState ? "settlement-btn active" : "settlement-btn disabled" }>结算</div>
</div>
</div>
</>
:
<div className="empty-cart">
亲, 您还没有选购任何商品<router-link to="/list">赶快去购物吧!</router-link>
</div>
}
</div>
</div>
)
}